2 Commits
Author SHA1 Message Date
Fr0zkaandClaude Opus 5.5 3e443bf81c wip(assembler): round L37 -- FI mouth carve-fade fix, no raised bridge (style 0); research R1
The passage mouth fade multiplied the signed carve distance by zero, which turned native rock into an
open surface (the reason earlier rounds stamped walk pads at mouths); it now blends between the
uncarved field and the full signed carve. The FI style-0 route followed a lifted grade envelope over
the crater (the "bridge"); it now runs on the rim and follows the native surface. New built-shape and
crease audits with thresholds frozen before the fix (L36 and box controls fail). 24/24 passage rows,
30/30 determinism, 16/16 invariants, 18/18 interface/ring. Unreal: one recipe setter (uncompiled).
Research R1 (read-only, web): ranked proposals for natural walkable mouths, floor-limited carve first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-23 05:51:50 +02:00
Fr0zkaandClaude Opus 5.5 8be1c4d9ef wip(assembler): round L36 -- tunnel floor without washboard; FI mouth without shards (evaluator v23)
The walk tread was built per segment as a one-sided density clamp, so its crossing with the native
field moved at segment joins (L35: 2.25-2.48 cm washboard). Treads now overlap up to 2 m as bounded
signed-distance slabs (0.06 cm residual). The FI mouth's crater detail and tread met without a smooth
join; v23 filters the local field, drops thin isolated solids and smooth-unions the tread with the
terrain. New floor ripple and mouth fragment/crease audits with L35 as failing controls. 24/24
passage rows, 30/30 determinism, 16/16 invariants, LOD0-8 rings; cost within noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-22 13:01:54 +02:00
58 changed files with 4514 additions and 101 deletions

No files matched your search

+24
View File
@@ -2138,6 +2138,30 @@ turn. Passage, capsule, support, open-air, bridge, binding, strict layout, captu
and cost evidence are recorded in and cost evidence are recorded in
[`Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md). [`Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md).
### 8.49 Walk-floor washboard and FI mouth naturalness — World Assembler M3 L36
L35's exported owner-SW mid-tunnel floor measured a 7.250 m repeat at 0.02252 m amplitude; its
upper-SW fixture arrival measured a 1.438 m repeat at 0.02482 m. Both came from segment-built,
one-sided support treads meeting the native terrain zero crossing at per-segment joins. The floor
probe samples the exported walk mesh along the resolved route, separately from the wall-radius scan.
Eligible tread sections overlap by up to 2 m and all sparse route segments now use a bounded
signed-distance slab, including SurfaceWorld-to-TunnelNetwork routes. This keeps the tread top stable
across segment joins while capsule-clearance and native-solid gates retain walkability and open air.
Regular tread tops remain seated 5 cm lower and ease back to 0.20 m within 20 m of a lower landing;
seeded shoulder grain is removed, and gravity-facing relief is reduced while side-wall and ceiling
relief remain irregular.
On v23, the same mesh probes measure 7.188 m / 0.00067 m (0.067 cm) on owner-SW and 6.500 m /
0.00057 m (0.057 cm) on the upper fixture. The density audit checks five 32 m walk-floor windows at
0.125 m spacing, rejects amplitudes of 0.015 m or more, and detects its beaded control. The FI mouth
uses a local 2 m symmetric signed-field filter with the route protected and joins route-mouth support
to native terrain with a 1.5 m smooth signed-distance union. Its mesh audit reduces the L35
42.27° fade-ring jump to 27.13° and finds no detached component above the 0.0001 m² area floor; one
8-triangle subthreshold component at 0.0000266 m² is recorded. The evaluator is version 23. Full
recipe/style audits, route support, open-air, strict layout, legacy validators, determinism, capture and
paired-cost results are in
[`Standalone/Research/WorldAssemblerM3/Linux36/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux36/REPORT.md).
## 9. Multiplayer model (listen-server first, dedicated-friendly) ## 9. Multiplayer model (listen-server first, dedicated-friendly)
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a > **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
+14 -1
View File
@@ -1250,11 +1250,24 @@ ARCHITECTURE §8.47 and
| `VF_FieldPassageWallRelief` | `Standalone/Core/src/VoxelForgeFieldShared.h` | Supplies passage-only irregular wall relief at rotated 43/29/19 m scales. Other `VF_FieldSurfaceRelief` users stay unchanged. | | `VF_FieldPassageWallRelief` | `Standalone/Core/src/VoxelForgeFieldShared.h` | Supplies passage-only irregular wall relief at rotated 43/29/19 m scales. Other `VF_FieldSurfaceRelief` users stay unchanged. |
| `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Catmull–Rom smooths eligible interior sweep joins, applies passage-only wall relief, and tapers/grains the entry support shoulder below the capsule/support probe footprint. | | `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Catmull–Rom smooths eligible interior sweep joins, applies passage-only wall relief, and tapers/grains the entry support shoulder below the capsule/support probe footprint. |
| `PlanResolvedLayoutPassagesV1` | `Standalone/Core/src/ResolvedLayoutInternal.h` | Gives each Cascade turn a seeded shallow radial profile instead of identical repeated full-depth cosine lobes. | | `PlanResolvedLayoutPassagesV1` | `Standalone/Core/src/ResolvedLayoutInternal.h` | Gives each Cascade turn a seeded shallow radial profile instead of identical repeated full-depth cosine lobes. |
| `auditPassageRipples` / `PassageRippleAudit` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples field wall radius at 0.125 m spacing, scans periods 0.5–8 m in 32 m windows, and rejects amplitudes ≥0.035 m. The negative control uses 2 m radius spheres at 1.5 m spacing. | | `auditPassageRipples` / `PassageRippleAudit` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples wall radii and walk-floor zero crossings at 0.125 m spacing, scans periods 0.5–8 m in 32 m windows, and rejects wall/floor amplitudes ≥0.035/0.015 m. Sphere and beaded-floor controls must be detected. |
`WorldAssemblyEvaluatorVersion=21`. See ARCHITECTURE §8.48 and `WorldAssemblyEvaluatorVersion=21`. See ARCHITECTURE §8.48 and
[`Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md). [`Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux35/REPORT.md).
### 3.32 Walk-floor washboard and FI mouth blend — World Assembler M3 L36
| Symbol | Canonical file | Role |
|---|---|---|
| `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Extends eligible tread sections by up to 2 m and uses bounded signed-distance support on every sparse route, including SurfaceWorld-to-TunnelNetwork, removes seeded shoulder grain, and seats regular tread tops 5 cm lower with a smooth return at the lower landing. Capsule/open-air gates remain. Gravity-facing wall relief is reduced; side and ceiling relief remain. |
| `ApplyResolvedStratePassageCarves` mouth support | `Standalone/Core/src/ShapeWorldInternal.h` | Builds exact signed-distance route-mouth tread boxes and smooth-unions them with native terrain over 1.5 m. |
| `NativeFloatingIslandDensityAt` | `Standalone/Core/src/ShapeWorldInternal.h` | Applies a local 2 m symmetric signed-field filter around FI passage mouths, exempts resolved route support, and removes isolated negative samples thinner than 2 m on all axes. |
| `auditPassageRipples` / `PassageRippleAudit` | `Standalone/Source/StreamBenchmarkPipeline.h` | Keeps the wall-radius audit and adds walk-floor zero-crossing profiles at 0.125 m, with five 32 m windows and a 0.015 m limit plus a beaded-floor negative control. |
| `RunMouthFragmentAuditL36.py`, `ProbeWalkFloorProfileL36.py` | `Standalone/Research/WorldAssemblerM3/Linux36/` | Compare L35 floor and FI-mouth controls with L36; the mouth check fails on detached components at or above 0.0001 m² and checks normal jumps across the fade ring. |
`WorldAssemblyEvaluatorVersion=23`. See ARCHITECTURE §8.49 and
[`Standalone/Research/WorldAssemblerM3/Linux36/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux36/REPORT.md).
## 4. The density pipeline (most-edited hot path) ## 4. The density pipeline (most-edited hot path)
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749 ### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
@@ -486,6 +486,7 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
IslandPlan.bottomChunkZ = Slot.BottomChunkZ; IslandPlan.bottomChunkZ = Slot.BottomChunkZ;
auto& Recipe = IslandPlan.manifest.recipe; auto& Recipe = IslandPlan.manifest.recipe;
Recipe.geometryVersion = 8; Recipe.geometryVersion = 8;
Recipe.terrainFollowingRoute = static_cast<std::uint32_t>(Passage.Style) == 0;
Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels; Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels;
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed()); Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Recipe.ownerVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0)); Recipe.ownerVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
@@ -250,8 +250,126 @@ namespace VoxelForgeCore
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256; inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2; inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
// Version 14 adds shared passage columns; version 16 smooths owner Gradient faces; // Version 14 adds shared passage columns; version 16 smooths owner Gradient faces;
// version 20 restores full Gradient amplitude and uses a continuous signed-distance band union. // version 20 restores full Gradient amplitude and uses a continuous signed-distance band union;
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 21; // version 22 overlaps tread sections and smooths FloatingIslands passage-mouth features;
// version 23 uses signed-distance support for sparse SurfaceWorld-to-TunnelNetwork treads;
// version 24 narrows passage-mouth support and filters native ground detail;
// version 25 joins open-mouth treads to native terrain relief before flattening inward;
// version 26 tapers mouth width and bounds the support to the native terrain surface;
// version 27 keeps the native blend through the entrance ramp and narrows its filter;
// version 28 tapers the open end to a worn path and blends it into walkable support;
// version 29 curves the supported and carved path through both mouth shoulders;
// version 30 narrows the flat core and blends support through a noisy natural shoulder;
// version 31 adds shared, capsule-safe field relief to both open-mouth walking surfaces;
// version 32 lowers the mouth core into the bore and removes thin filtered-field remnants;
// version 33 fades relief before small detached field remnants;
// version 34 keeps the open-mouth tread within native terrain curvature and noise;
// version 35 follows the native heightfield farther into the mouth ramp;
// version 36 protects the walking line with a gradual filter fade;
// version 37 lengthens that fade and restores the last capsule margin;
// version 38 keeps native ground at the lip and blends the tread in below it;
// version 39 bends the mouth tread across the native field and restores the open bore;
// version 40 uses broad route-only filtering and smooth, low-frequency tread relief;
// version 41 rounds the mouth tread and removes filter route masks;
// version 42 clips the mouth bore at the native walking surface;
// version 43 localizes the mouth field filter without masking the surface route;
// version 44 widens the natural shoulder blend and follows lower-frequency terrain;
// version 45 tests the passage tread independently of route-anchor support;
// version 46 fades the native filter broadly along the authored surface path;
// version 47 carries the full winding route with a narrow native-height tread;
// version 48 fades route support out before the surface mouth;
// version 49 keeps the mouth filtered and restores detail on the outer route;
// version 50 limits route-field restoration to 65% of the convolution weight;
// version 51 narrows the route fade to 8 m horizontal and 6 m vertical;
// version 52 evaluates mouth support in positive-density route tiles;
// version 53 fades the supported passage tread in from native ground at both mouths;
// version 54 tapers mouth-support width and depth;
// version 55 blends that tread from native ground and applies route support after carving;
// version 56 seats route support at the audited floor;
// version 57's raised lip overfilled the mouth;
// version 58 restores the native-height lip and explicitly covers route-support queries;
// version 59 fixes route-support distance units and lengthens the worn-mouth taper;
// version 60 brings the narrow terrain-following route tread in before the outer mouth lip;
// version 61 finishes that tread by five metres and preserves its exact walking boundary;
// version 62 fades the route tread along the authored path and seats it below the capsule;
// version 63 only adds that tread through native-air gaps and fades it over two path metres;
// version 64 rounds the path-to-ground join while keeping its top below the capsule;
// version 65 tests rounded tread cross-sections and softer native-height following;
// version 66 fades the mouth carve and support together so native crater ground forms the lip;
// version 67 follows the native route height more closely and completes the entrance fade sooner;
// version 68 narrows the flat walk core and eases a shallow worn ramp into the cave floor;
// version 69 lets capsule-cleared mouth treads bridge positive-density floor gaps;
// version 70 lowers the mouth tread enough to clear the capsule's toe margin;
// version 71 keeps its rounded top inside the route's shallow support-probe band;
// version 72 adds mouth support only below its signed floor, avoiding capsule-space fill;
// version 73 carries the terrain-following route tread to the actual passage mouth;
// version 74 seats that short connection in the audited shallow support band;
// version 75 keeps the rounded path edge outside the capsule's shallow floor probes;
// version 76 carries the endpoint tread through the passage's first support-probe depth;
// version 77 joins the mouth floor to native ground through a noisy walk ramp;
// version 78 rounds the ramp shoulder and clears its initial capsule envelope;
// version 79 tested the unbuilt native-ground mouth;
// version 80 narrows the walk tread;
// version 81 adds bounded path relief and a wavering, worn shoulder;
// version 82 lowers the relief into the tread to keep its capsule clearance;
// version 83 increases the natural edge warp and recessed surface variation;
// version 84 moves that variation to the actual mouth and restores support width;
// version 85 keeps the established lower-mouth capsule profile while shaping entry mouths;
// version 86 restricts route reinforcement to native solid and recesses the worn top;
// version 87 restores its pre-carve route placement.
// version 88 removes the independent mouth cap; the native crater surface forms the entry ramp.
// version 89 removes the one-sided entry tread band as well.
// version 90 lengthens the entrance floor-carve fade to retain native ground beneath the route.
// version 91 lets a narrow mouth support follow the full native surface relief.
// version 92 derives mouth floor alignment from the entrance path for first-segment samples too.
// version 93 blends the carve against native density so a zero fade really preserves ground.
// version 94 applies the mouth floor fade only beneath the walking surface.
// version 95 seats retained native ground just below the capsule's foot clearance.
// version 96 limits that preservation to the shallow support band, away from inner coils.
// version 97 extends the native support band down to the sampled rock envelope.
// version 98 raises the support band back to the route floor while keeping it below the capsule.
// version 99 restores native-solid route support after the passage carve;
// version 100 restores a shallow, noise-shaped entrance tread under its capsule gate;
// version 101 preserves the native FI field around the resolved walking line;
// version 102 tapers the mouth filter from its 1.5 m core to the same 12.5 m edge;
// version 103 blends route protection over a metre-scale shoulder;
// version 104 composes overlapping route masks once, by maximum influence;
// version 105 widens the route blend to soften the native/filter seam;
// version 106 preserves raw negative rock under the route without unfiltering its crown;
// version 107 softens that restore and broadens the narrow upper-coil walk core;
// version 108 deepens relief and lowers route restoration to soften the remaining crease;
// version 109 blended 60% of the two-metre mouth field convolution;
// version 110 restores the full filtered contour and widens the upper walk core;
// version 111 permits the capsule-cleared signed tread to bridge the upper mouth gap;
// version 112 adds a tenth-metre margin to the rounded upper tread edge.
// version 139 keeps native ground only in a capsule-scale mouth lane and carries
// the seeded terrain contour through the full upper-mouth support;
// versions 140-145 tested short-span decks, mouth fade length, route phase and tread SDF;
// versions 146-148 tested a sunk chord, near-side phase and all-style FI spiral.
// version 149 routes non-spiral FI mouths into a tight near-side ring.
// version 150 widened that ring but left a central pillar.
// version 151 keeps the ring half a metre outside the passage bore.
// version 152 fills capsule-clear positive-density gaps under the narrow mouth tread.
// version 153 tested a one-metre entry-turn sink; it left the raised route visible and rippled the floor.
// version 154 tightens the FI ring and uses a steeper, still walkable descent to separate hidden coils.
// version 155's tighter coil removed the fin but made sharp fragments and route collisions.
// version 156 keeps the 3 m ring, overlaps the bore by 0.25 m, and seats the native tread below the capsule.
// version 157 joins the near-side FI ring at a tangent so its entrance does not kink across the crater.
// version 158 tested a descending entry coil; its exposed canopy is rejected.
// version 159 keeps the tangent and uses a steeper sub-15-degree FI descent to separate its coils.
// version 160 removed the mouth walk lane; it left the bridge and lost one support sample.
// version 161 tapered the anchored entry radius to 3 m; a fin remained at the crater lip.
// version 162 disabled native-tread restoration alone; the carve fade still preserved the ridge.
// version 163 disabled both passage restorations; it removed the path support.
// version 164 narrowed the first two inner FI route points; it enlarged the lip fin.
// version 165 disabled native-rock blending around the FI stratum route; the ridge remained.
// version 166 disabled the FI mouth support handoff; route support failed on the island approach.
// version 167 tested centerline footing on the FI approach; its bridge remained and floor ripple rose.
// version 168 removed the connector's 1 m lift; the crater crossing remained exposed.
// version 169 sank every connector 2 m; it left four FI route support probes low.
// version 170 sinks only connectors touching the anchored root by 1 m.
// Version 208 keeps the crater tread scoped to style-zero FI mouths.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 208;
enum class FieldChannel : std::uint32_t enum class FieldChannel : std::uint32_t
{ {
@@ -445,6 +563,8 @@ namespace VoxelForgeCore
std::int64_t anchorIslandMinimumRadiusSubvoxels = 0; std::int64_t anchorIslandMinimumRadiusSubvoxels = 0;
std::int64_t anchorRootBottomZSubvoxels = 0; std::int64_t anchorRootBottomZSubvoxels = 0;
std::int64_t anchorMouthOffsetSubvoxels = 0; std::int64_t anchorMouthOffsetSubvoxels = 0;
// Style-zero mouths follow the sampled island surface into the passage lip.
bool terrainFollowingRoute = false;
WorldAssemblyRouteConstraintsV1 routeConstraints; WorldAssemblyRouteConstraintsV1 routeConstraints;
std::uint64_t contentHash = 0; std::uint64_t contentHash = 0;
}; };
@@ -669,6 +789,8 @@ namespace VoxelForgeCore
std::string stableId; std::string stableId;
std::int32_t upperStrateIndex = -1; std::int32_t upperStrateIndex = -1;
std::int32_t lowerStrateIndex = -1; std::int32_t lowerStrateIndex = -1;
// EVoxelPassageStyle numeric value used by style-specific passage support.
std::uint32_t style = 1;
std::int64_t placementRadiusSubvoxels = 0; std::int64_t placementRadiusSubvoxels = 0;
std::array<std::int64_t, 3> upperMouthSubvoxels{}; std::array<std::int64_t, 3> upperMouthSubvoxels{};
std::array<std::int64_t, 3> lowerMouthSubvoxels{}; std::array<std::int64_t, 3> lowerMouthSubvoxels{};
+72 -4
View File
@@ -203,6 +203,7 @@ namespace floating_island_detail
HashWord(hash, static_cast<std::uint64_t>(recipe.boundarySealSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.boundarySealSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.bridgeHalfWidthSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.bridgeHalfWidthSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.bridgeThicknessSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.bridgeThicknessSubvoxels), 8);
if(recipe.terrainFollowingRoute)HashWord(hash,1,1);
HashWord(hash, HashRouteConstraints(recipe.routeConstraints), 8); HashWord(hash, HashRouteConstraints(recipe.routeConstraints), 8);
return hash; return hash;
} }
@@ -805,7 +806,37 @@ namespace floating_island_detail
const double t = std::clamp(along / length, 0.0, 1.0); const double t = std::clamp(along / length, 0.0, 1.0);
if (alongFraction) *alongFraction = t; if (alongFraction) *alongFraction = t;
const double deck = az + (static_cast<double>(edge.endSubvoxels[2]) - az) * t; const double deck = az + (static_cast<double>(edge.endSubvoxels[2]) - az) * t;
const std::uint64_t westernEdgeId = StableId(recipe.seed, 0x200000000ull,
0x425249444745ull);
const bool westernSupportConnector=edge.stableId==westernEdgeId;
if(recipe.geometryVersion>=2&&WorldAssemblyEvaluatorVersion>=183
&&westernSupportConnector)
{
const double phase=static_cast<double>(Roll(recipe.seed,edge.stableId,0x5749445448ull)
&0xffffffffull)*(2.0*Pi/4294967296.0);
const double localHalfWidth=std::max(
static_cast<double>(recipe.routeConstraints.capsuleRadiusSubvoxels+Q),
static_cast<double>(edge.halfWidthSubvoxels)
+0.48*static_cast<double>(Q)*std::sin(6.0*Pi*t+phase));
const double localThickness=static_cast<double>(edge.thicknessSubvoxels);
const double rounding=std::min({1.5*static_cast<double>(Q),
localHalfWidth*0.6,localThickness*0.45});
const double roundedX=side-(localHalfWidth-rounding);
const double roundedY=std::abs(along-0.5*length)-(0.5*length-rounding);
const double roundedZ=std::abs(z-(deck-0.5*localThickness))
-(0.5*localThickness-rounding);
const double roundedOutside=std::sqrt(
std::pow(std::max(roundedX,0.0),2)
+std::pow(std::max(roundedY,0.0),2)
+std::pow(std::max(roundedZ,0.0),2));
const double roundedInside=std::min(std::max(roundedX,
std::max(roundedY,roundedZ)),0.0);
if(topSurface)*topSurface=z-deck>=std::max(horizontal,
deck-localThickness-z);
return roundedOutside+roundedInside-rounding;
}
const double arch = recipe.geometryVersion >= 2 const double arch = recipe.geometryVersion >= 2
&& WorldAssemblyEvaluatorVersion < 172
? 2.0 * static_cast<double>(edge.thicknessSubvoxels) * (4.0 * t * (1.0 - t)) ? 2.0 * static_cast<double>(edge.thicknessSubvoxels) * (4.0 * t * (1.0 - t))
: 0.0; : 0.0;
const double upper = z - deck; const double upper = z - deck;
@@ -1174,8 +1205,8 @@ namespace floating_island_detail
// Land each connector well inside the broad top so the full capsule footprint remains // 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. // supported across the bridge-to-island transition, including bounded surface roughness.
// Les connecteurs recouvrent le plateau pour soutenir toute la capsule au raccord. // Les connecteurs recouvrent le plateau pour soutenir toute la capsule au raccord.
// Keep the narrow span exposed to open sky; a smaller fraction buries most of it in the island tops. // Keep the capsule footprint supported while seating L37 connectors just below the native island tops.
// Garder la passerelle etroite visible dans le vide ; une fraction plus faible l'enfouit dans les plateaux. // Garder l'appui de la capsule tout en enfouissant les connecteurs L37 juste sous les plateaux.
const double landingFraction = recipe.geometryVersion >= 2 ? 0.52 : 0.62; const double landingFraction = recipe.geometryVersion >= 2 ? 0.52 : 0.62;
const double fromReach = static_cast<double>(from.radiusSubvoxels) * landingFraction; const double fromReach = static_cast<double>(from.radiusSubvoxels) * landingFraction;
const double toReach = static_cast<double>(to.radiusSubvoxels) * landingFraction; const double toReach = static_cast<double>(to.radiusSubvoxels) * landingFraction;
@@ -1183,7 +1214,19 @@ namespace floating_island_detail
const double sy = from.centerSubvoxels[1] + uy * fromReach; const double sy = from.centerSubvoxels[1] + uy * fromReach;
const double ex = to.centerSubvoxels[0] - ux * toReach; const double ex = to.centerSubvoxels[0] - ux * toReach;
const double ey = to.centerSubvoxels[1] - uy * toReach; const double ey = to.centerSubvoxels[1] - uy * toReach;
const double routeDeckLift = recipe.geometryVersion >= 2 ? 1.0 * Q : 0.0; const bool craterMouthConnector=WorldAssemblyEvaluatorVersion>=174
&&(from.ordinal==recipe.anchorIslandOrdinal
||to.ordinal==recipe.anchorIslandOrdinal);
const bool westernSupportConnector=WorldAssemblyEvaluatorVersion>=178
&&((from.ordinal==0&&to.ordinal==1)||(from.ordinal==1&&to.ordinal==0));
const double routeDeckLift=recipe.geometryVersion>=2
?(WorldAssemblyEvaluatorVersion>=178
?(westernSupportConnector
?(WorldAssemblyEvaluatorVersion>=181?-1.0
:(WorldAssemblyEvaluatorVersion>=180?-1.25
:(WorldAssemblyEvaluatorVersion>=179?-1.5:-1.0))):-2.0)
:(craterMouthConnector?-2.0
:(WorldAssemblyEvaluatorVersion>=172?-1.0:1.0)))*Q:0.0;
const double sz = IslandTopSurface(recipe, from, sx, sy) + routeDeckLift; const double sz = IslandTopSurface(recipe, from, sx, sy) + routeDeckLift;
const double ez = IslandTopSurface(recipe, to, ex, ey) + routeDeckLift; const double ez = IslandTopSurface(recipe, to, ex, ey) + routeDeckLift;
edge.startSubvoxels = {RoundQ(sx), RoundQ(sy), RoundQ(sz)}; edge.startSubvoxels = {RoundQ(sx), RoundQ(sy), RoundQ(sz)};
@@ -1336,6 +1379,7 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
manifest.recipe.anchorMouthOffsetSubvoxels)) manifest.recipe.anchorMouthOffsetSubvoxels))
{error="floating-island geometry version 7 is missing a passage-rooted anchor field";return std::nullopt;} {error="floating-island geometry version 7 is missing a passage-rooted anchor field";return std::nullopt;}
} }
manifest.recipe.terrainFollowingRoute=recipeValue.Get("terrain_following_route").Boolean(false);
if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema) if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema)
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash) || !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("schema_version"), manifest.recipe.schemaVersion)
@@ -1495,6 +1539,7 @@ std::string FloatingIslandManifestV1::ToJson() const
<< ",\"anchor_island_minimum_radius_q\":" << recipe.anchorIslandMinimumRadiusSubvoxels << ",\"anchor_island_minimum_radius_q\":" << recipe.anchorIslandMinimumRadiusSubvoxels
<< ",\"anchor_root_bottom_z_q\":" << recipe.anchorRootBottomZSubvoxels << ",\"anchor_root_bottom_z_q\":" << recipe.anchorRootBottomZSubvoxels
<< ",\"anchor_mouth_offset_q\":" << recipe.anchorMouthOffsetSubvoxels; << ",\"anchor_mouth_offset_q\":" << recipe.anchorMouthOffsetSubvoxels;
if(recipe.terrainFollowingRoute)out << ",\"terrain_following_route\":true";
out << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels out << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels
<< ",\"contour_warp_q\":" << recipe.contourWarpSubvoxels << ",\"contour_warp_q\":" << recipe.contourWarpSubvoxels
<< ",\"roughness_q\":" << recipe.roughnessSubvoxels << ",\"roughness_q\":" << recipe.roughnessSubvoxels
@@ -1746,6 +1791,26 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
const std::int64_t y = floating_island_detail::RoundQ( const std::int64_t y = floating_island_detail::RoundQ(
static_cast<double>(a.centerSubvoxels[1]) + dy * t); static_cast<double>(a.centerSubvoxels[1]) + dy * t);
std::int64_t routeX=x,routeY=y; std::int64_t routeX=x,routeY=y;
if(WorldAssemblyEvaluatorVersion>=192&&recipe.terrainFollowingRoute
&&recipe.geometryVersion>=5
&&segment+2==count&&pieces>1)
{
const auto& anchor=islands[recipe.anchorIslandOrdinal];
const std::uint32_t side=static_cast<std::uint32_t>(
floating_island_detail::UnitQ16(recipe.seed,anchor.stableId,
0x414e43484f524d50ull)&1u);
const double length=std::hypot(dx,dy);
const double bend=10.0*floating_island_detail::Q
*std::pow(std::sin(floating_island_detail::Pi*t),2.0);
const double sign=side?-1.0:1.0;
if(length>0.0)
{
routeX=floating_island_detail::RoundQ(
static_cast<double>(routeX)+sign*(-dy/length)*bend);
routeY=floating_island_detail::RoundQ(
static_cast<double>(routeY)+sign*(dx/length)*bend);
}
}
if(recipe.geometryVersion>=5&&segment+2==count&&piece==pieces) if(recipe.geometryVersion>=5&&segment+2==count&&piece==pieces)
{ {
const auto& anchor=islands[recipe.anchorIslandOrdinal]; const auto& anchor=islands[recipe.anchorIslandOrdinal];
@@ -1762,7 +1827,10 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
routeX=floating_island_detail::RoundQ(mouthX); routeX=floating_island_detail::RoundQ(mouthX);
routeY=floating_island_detail::RoundQ(mouthY); routeY=floating_island_detail::RoundQ(mouthY);
} }
const auto surface = floating_island_detail::FindCapsuleFootingTopZ( const auto surface = WorldAssemblyEvaluatorVersion>=199
&&recipe.terrainFollowingRoute
?floating_island_detail::FindSurfaceZ(recipe,manifest.plan,routeX,routeY)
:floating_island_detail::FindCapsuleFootingTopZ(
recipe,manifest.plan,routeX,routeY); recipe,manifest.plan,routeX,routeY);
if (!surface) if (!surface)
{ error = "island planner could not find a full capsule-width support surface along its connector chain"; return std::nullopt; } { error = "island planner could not find a full capsule-width support surface along its connector chain"; return std::nullopt; }
+69 -9
View File
@@ -221,6 +221,7 @@ namespace
out<<"{\"stable_id\":"<<LayoutEscape(p.stableId) out<<"{\"stable_id\":"<<LayoutEscape(p.stableId)
<<",\"upper_strate_index\":"<<p.upperStrateIndex <<",\"upper_strate_index\":"<<p.upperStrateIndex
<<",\"lower_strate_index\":"<<p.lowerStrateIndex <<",\"lower_strate_index\":"<<p.lowerStrateIndex
<<",\"style\":"<<p.style
<<",\"placement_radius_q\":"<<p.placementRadiusSubvoxels <<",\"placement_radius_q\":"<<p.placementRadiusSubvoxels
<<",\"upper_mouth_q\":["<<p.upperMouthSubvoxels[0]<<',' <<",\"upper_mouth_q\":["<<p.upperMouthSubvoxels[0]<<','
<<p.upperMouthSubvoxels[1]<<','<<p.upperMouthSubvoxels[2] <<p.upperMouthSubvoxels[1]<<','<<p.upperMouthSubvoxels[2]
@@ -683,6 +684,8 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
ResolvedLayoutPassageV1 p;p.stableId=LayoutString(v,"stable_id"); ResolvedLayoutPassageV1 p;p.stableId=LayoutString(v,"stable_id");
p.upperStrateIndex=static_cast<std::int32_t>(LayoutInt(v,"upper_strate_index")); p.upperStrateIndex=static_cast<std::int32_t>(LayoutInt(v,"upper_strate_index"));
p.lowerStrateIndex=static_cast<std::int32_t>(LayoutInt(v,"lower_strate_index")); p.lowerStrateIndex=static_cast<std::int32_t>(LayoutInt(v,"lower_strate_index"));
if(v.Get("style").IsNumber())
p.style=static_cast<std::uint32_t>(LayoutInt(v,"style"));
p.placementRadiusSubvoxels=LayoutInt(v,"placement_radius_q"); p.placementRadiusSubvoxels=LayoutInt(v,"placement_radius_q");
p.mouthRadiusSubvoxels=LayoutInt(v,"mouth_radius_q");p.midRadiusSubvoxels=LayoutInt(v,"mid_radius_q"); p.mouthRadiusSubvoxels=LayoutInt(v,"mouth_radius_q");p.midRadiusSubvoxels=LayoutInt(v,"mid_radius_q");
const auto& upper=v.Get("upper_mouth_q").AsArray(); const auto& upper=v.Get("upper_mouth_q").AsArray();
@@ -808,6 +811,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
for(std::uint32_t connection=0;connection<connectionCount;++connection) for(std::uint32_t connection=0;connection<connectionCount;++connection)
{ {
ResolvedLayoutPassageV1 passage; ResolvedLayoutPassageV1 passage;
passage.style=style;
passage.stableId="passage/"+std::to_string(upper.strateIndex)+"-" passage.stableId="passage/"+std::to_string(upper.strateIndex)+"-"
+std::to_string(lower.strateIndex)+"/"+std::to_string(connection); +std::to_string(lower.strateIndex)+"/"+std::to_string(connection);
passage.upperStrateIndex=upper.strateIndex;passage.lowerStrateIndex=lower.strateIndex; passage.upperStrateIndex=upper.strateIndex;passage.lowerStrateIndex=lower.strateIndex;
@@ -886,22 +890,45 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
std::max(ringRadius,clearRingRadius)); std::max(ringRadius,clearRingRadius));
} }
if(anchoredIslandPassage) if(anchoredIslandPassage)
ringRadius=std::min(ringRadius,mouthRadius+qPerMetre/2); {
const std::int64_t craterRimRouteRadius=VoxelForgeCore::WorldAssemblyEvaluatorVersion>=185
&&style==0&&!anchoredSpiral&&passageAnchor&&passageAnchor->values.size()>3
?std::max<std::int64_t>(midRadius,
passageAnchor->values[3]+2*qPerMetre)
:mouthRadius+qPerMetre/2;
ringRadius=std::min(ringRadius,anchoredSpiral
?mouthRadius+qPerMetre/2:craterRimRouteRadius);
}
std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed, std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed,
upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16); upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16);
if(hasUpperRoute) if(hasUpperRoute)
{ {
std::int64_t bestAlignment=std::numeric_limits<std::int64_t>::min(); std::int64_t bestAlignment=std::numeric_limits<std::int64_t>::min();
const std::uint32_t hashedPhase=phase; const std::uint32_t hashedPhase=phase;
const std::int64_t phaseX=style==0&&anchoredIslandPassage
&&!anchoredSpiral&&passageAnchor
?upperMouth[0]-passageAnchor->values[0]
:(hasUpperTangent?upperTangent[0]:upperMouth[0]);
const std::int64_t phaseY=style==0&&anchoredIslandPassage
&&!anchoredSpiral&&passageAnchor
?upperMouth[1]-passageAnchor->values[1]
:(hasUpperTangent?upperTangent[1]:upperMouth[1]);
for(std::uint32_t offset=0;offset<16;++offset) for(std::uint32_t offset=0;offset<16;++offset)
{ {
const std::uint32_t candidate=static_cast<std::uint32_t>( const std::uint32_t candidate=static_cast<std::uint32_t>(
(hashedPhase+offset)%16); (hashedPhase+offset)%16);
const std::int64_t alignment=(hasUpperTangent?upperTangent[0]:upperMouth[0]) const std::int64_t alignment=phaseX*directions[candidate][0]
*directions[candidate][0] +phaseY*directions[candidate][1];
+(hasUpperTangent?upperTangent[1]:upperMouth[1]) if(style==0&&anchoredIslandPassage&&!anchoredSpiral&&passageAnchor)
*directions[candidate][1]; {
if(alignment>bestAlignment) const std::int64_t targetProjection=ringRadius*65536;
if(alignment<=0)continue;
const std::int64_t tangentError=std::abs(alignment-targetProjection);
if(tangentError<bestAlignment
||bestAlignment==std::numeric_limits<std::int64_t>::min())
{bestAlignment=tangentError;phase=candidate;}
}
else if(alignment>bestAlignment)
{bestAlignment=alignment;phase=candidate;} {bestAlignment=alignment;phase=candidate;}
} }
} }
@@ -924,7 +951,18 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
} }
bool clockwise=(PassageSeed(seed,upper.strateIndex,lower.strateIndex, bool clockwise=(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
connection,0x44495245u)&1u)!=0; connection,0x44495245u)&1u)!=0;
if(anchoredSpiral&&passageAnchor&&hasUpperTangent) if(style==0&&anchoredIslandPassage&&!anchoredSpiral&&passageAnchor)
{
const auto& ringDirection=directions[phase];
const double radiusX=static_cast<double>(ringRadius)*ringDirection[0]/65536.0;
const double radiusY=static_cast<double>(ringRadius)*ringDirection[1]/65536.0;
const double anchorX=static_cast<double>(passageAnchor->values[0]);
const double anchorY=static_cast<double>(passageAnchor->values[1]);
const double connectorX=anchorX+radiusX-upperMouth[0];
const double connectorY=anchorY+radiusY-upperMouth[1];
clockwise=connectorX*(-radiusY)+connectorY*radiusX>=0.0;
}
else if(anchoredSpiral&&passageAnchor&&hasUpperTangent)
{ {
const double radialX=static_cast<double>(upperMouth[0]-passageAnchor->values[0]); const double radialX=static_cast<double>(upperMouth[0]-passageAnchor->values[0]);
const double radialY=static_cast<double>(upperMouth[1]-passageAnchor->values[1]); const double radialY=static_cast<double>(upperMouth[1]-passageAnchor->values[1]);
@@ -1068,7 +1106,8 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
hasAnchorHelix=helixEndIndex>0; hasAnchorHelix=helixEndIndex>0;
} }
const std::int64_t routeGradeQ16=hasAnchorHelix const std::int64_t routeGradeQ16=hasAnchorHelix
&&lower.generatorKind=="SurfaceWorld"?8000:TargetGradeQ16; &&lower.generatorKind=="SurfaceWorld"?8000
:style==0&&anchoredIslandPassage&&!anchoredSpiral?15500:TargetGradeQ16;
const std::array<std::int64_t,2> firstRing=ringPoint(phase); const std::array<std::int64_t,2> firstRing=ringPoint(phase);
if(hasAnchorHelix&&pathStyle==1u) if(hasAnchorHelix&&pathStyle==1u)
{ {
@@ -1168,7 +1207,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
pushXY(ringPoint(lowerPhase)); pushXY(ringPoint(lowerPhase));
} }
pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath(); pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath();
const std::size_t lowerMouthIndex=xy.size()-1; std::size_t lowerMouthIndex=xy.size()-1;
std::vector<std::array<std::int64_t,3>> lowerLandingPoints; std::vector<std::array<std::int64_t,3>> lowerLandingPoints;
if(hasLowerTangent) if(hasLowerTangent)
{ {
@@ -1202,6 +1241,27 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
if(lowerLandingPoints.empty()) if(lowerLandingPoints.empty())
{error=passage.stableId+" lower route has no landing length inside PassageConfig.DistanceMax";return false;} {error=passage.stableId+" lower route has no landing length inside PassageConfig.DistanceMax";return false;}
} }
if(VoxelForgeCore::WorldAssemblyEvaluatorVersion>=188
&&style==0&&anchoredIslandPassage&&!anchoredSpiral&&xy.size()>4)
{
std::vector<std::array<std::int64_t,2>> refined;
refined.reserve(xy.size()*2);
for(std::size_t index=0;index+1<xy.size();++index)
{
refined.push_back(xy[index]);
if(index<3)continue;
refined.push_back({
static_cast<std::int64_t>(std::llround(0.5*(
static_cast<double>(xy[index][0])
+static_cast<double>(xy[index+1][0])))),
static_cast<std::int64_t>(std::llround(0.5*(
static_cast<double>(xy[index][1])
+static_cast<double>(xy[index+1][1]))))});
}
refined.push_back(xy.back());
if(lowerMouthIndex>3)lowerMouthIndex+=lowerMouthIndex-3;
xy.swap(refined);
}
totalHorizontal=measurePath(); totalHorizontal=measurePath();
if(loops>=256||totalHorizontal==0) if(loops>=256||totalHorizontal==0)
{error=passage.stableId+" could not fit a supported grade inside the resolved placement radius";return false;} {error=passage.stableId+" could not fit a supported grade inside the resolved placement radius";return false;}
+663 -81
View File
@@ -94,6 +94,9 @@ public:
} }
bool hasMouthSupport(std::size_t passageIndex) const bool hasMouthSupport(std::size_t passageIndex) const
{return passageIndex<passageMouthSupports_.size()&&passageMouthSupports_[passageIndex]!=0;} {return passageIndex<passageMouthSupports_.size()&&passageMouthSupports_[passageIndex]!=0;}
bool hasAnyMouthSupport() const
{return std::any_of(passageMouthSupports_.begin(),passageMouthSupports_.end(),
[](std::uint8_t value){return value!=0;});}
bool hasRouteFeatureOverlap() const{return routeFeatureOverlap_;} bool hasRouteFeatureOverlap() const{return routeFeatureOverlap_;}
bool hasColumnZOverlap() const{return columnZOverlap_;} bool hasColumnZOverlap() const{return columnZOverlap_;}
bool mayContainResolvedBoundaryBand() const{return mayContainResolvedBoundaryBand_;} bool mayContainResolvedBoundaryBand() const{return mayContainResolvedBoundaryBand_;}
@@ -683,23 +686,36 @@ public:
{ {
if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X) if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X)
||!std::isfinite(Y)||!std::isfinite(Z))return density; ||!std::isfinite(Y)||!std::isfinite(Z))return density;
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double qPerMetre=Q*1000000.0/
std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
const double qx=static_cast<double>(X)*Q,qy=static_cast<double>(Y)*Q,
qz=static_cast<double>(Z)*Q;
const auto* passageTileQueries=ActivePassageTileQueries; const auto* passageTileQueries=ActivePassageTileQueries;
const bool passageTileScopeMatches=passageTileQueries const bool passageTileScopeMatches=passageTileQueries
&&passageTileQueries->matches(*this); &&passageTileQueries->matches(*this);
bool queryNearUpperMouthSupport=false;
for(const auto& passage:ResolvedLayout->passages)
{
const double dx=qx-static_cast<double>(passage.upperMouthSubvoxels[0]);
const double dy=qy-static_cast<double>(passage.upperMouthSubvoxels[1]);
const double dz=qz-static_cast<double>(passage.upperMouthSubvoxels[2]);
const double reach=15.0*qPerMetre;
if(dx*dx+dy*dy<=reach*reach&&std::abs(dz)<=8.0*qPerMetre)
{queryNearUpperMouthSupport=true;break;}
}
if(passageTileScopeMatches&&density>=0.0f if(passageTileScopeMatches&&density>=0.0f
&&!passageTileQueries->hasRouteFeatureOverlap() &&!passageTileQueries->hasRouteFeatureOverlap()
&&!passageTileQueries->hasColumnZOverlap())return density; &&!passageTileQueries->hasColumnZOverlap()
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); &&!passageTileQueries->hasAnyMouthSupport()
const double qx=static_cast<double>(X)*Q,qy=static_cast<double>(Y)*Q,qz=static_cast<double>(Z)*Q; &&!queryNearUpperMouthSupport)return density;
const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
const double floorThicknessVoxels=qPerMetre/Q; const double floorThicknessVoxels=qPerMetre/Q;
const double playerCenterAboveFloorQ=1.13*qPerMetre; const double playerCenterAboveFloorQ=1.13*qPerMetre;
float composed=density; float composed=density;
const auto composeRouteMouthSupport=[&](std::int32_t strateIndex, const auto composeRouteMouthSupport=[&](std::int32_t strateIndex,
const std::array<std::int64_t,3>& mouth,bool departure) const std::array<std::int64_t,3>& mouth,bool departure)
{ {
// Support is allowed to reinforce native solid terrain only. Without this // Surface-route treads may reinforce native solid terrain only.
// guard the route-point tread stamps a floating shelf into an open stratum.
if(!(density<0.0f))return; if(!(density<0.0f))return;
const double mouthDx=qx-static_cast<double>(mouth[0]); const double mouthDx=qx-static_cast<double>(mouth[0]);
const double mouthDy=qy-static_cast<double>(mouth[1]); const double mouthDy=qy-static_cast<double>(mouth[1]);
@@ -727,8 +743,9 @@ public:
} }
if(routeCount<2)return; if(routeCount<2)return;
const std::size_t lastSegment=routeCount-1; const std::size_t lastSegment=routeCount-1;
const double floorThickness=1000000.0/std::max<std::uint32_t>(1, constexpr double MouthSupportBlendMetres=0.45;
ResolvedLayout->coreVoxelSizeMicrometres); const double mouthBlendWidth=MouthSupportBlendMetres*qPerMetre;
double mouthSupportDistance=std::numeric_limits<double>::infinity();
for(std::size_t index=0;index<lastSegment;++index) for(std::size_t index=0;index<lastSegment;++index)
{ {
const auto& a=*route[index];const auto& b=*route[index+1]; const auto& a=*route[index];const auto& b=*route[index+1];
@@ -741,30 +758,49 @@ public:
const double centerZ=a.values[2]+t*(static_cast<double>(b.values[2])-a.values[2]); const double centerZ=a.values[2]+t*(static_cast<double>(b.values[2])-a.values[2]);
const double routeRadius=a.values.size()>=4 const double routeRadius=a.values.size()>=4
?a.values[3]+t*(static_cast<double>(b.values[3])-a.values[3]):0.0; ?a.values[3]+t*(static_cast<double>(b.values[3])-a.values[3]):0.0;
const double halfWidth=std::max(0.75*qPerMetre,routeRadius); const double halfWidth=std::clamp(routeRadius,
0.42*qPerMetre,0.56*qPerMetre);
const double horizontalX=qx-centerX,horizontalY=qy-centerY; const double horizontalX=qx-centerX,horizontalY=qy-centerY;
if(horizontalX*horizontalX+horizontalY*horizontalY>halfWidth*halfWidth)continue; const double horizontalDistance=DeterministicSqrt(
const double floorVoxels=(centerZ-1.20*qPerMetre)/Q; horizontalX*horizontalX+horizontalY*horizontalY);
if(Z<floorVoxels-floorThickness||Z>floorVoxels+1.0/Q)continue; const double floorQ=centerZ-(VoxelForgeCore::WorldAssemblyEvaluatorVersion>=184
composed=std::min(composed,static_cast<float>( ?playerCenterAboveFloorQ+0.25*qPerMetre:1.20*qPerMetre);
std::clamp(Z-floorVoxels,-floorThickness,0.0))); const double sideDistance=horizontalDistance-halfWidth;
const double topDistance=qz-floorQ;
const double bottomDistance=floorQ-qPerMetre-qz;
const double verticalDistance=std::max(topDistance,bottomDistance);
if(sideDistance>mouthBlendWidth||verticalDistance>mouthBlendWidth)continue;
const double outside=DeterministicSqrt(
std::max(sideDistance,0.0)*std::max(sideDistance,0.0)
+std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0));
const double inside=std::min(std::max(sideDistance,verticalDistance),0.0);
mouthSupportDistance=std::min(mouthSupportDistance,outside+inside);
} }
if(std::isfinite(mouthSupportDistance))
composed=SmoothMinDistance(composed,
static_cast<float>(mouthSupportDistance/Q),
static_cast<float>(mouthBlendWidth/Q));
}; };
// Keep the native surface route connected to the entrance. Passage carving // The connected passage tread continues the surface route into the tunnel.
// then removes only the parts that overlap the tunnel, whose own tread owns
// the interior ramp.
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
{
const auto& passage=ResolvedLayout->passages[passageIndex];
const bool tileHasSupport=passageTileScopeMatches;
if(!tileHasSupport||ActivePassageTileQueries->hasMouthSupport(passageIndex))
composeRouteMouthSupport(passage.upperStrateIndex,
passage.upperMouthSubvoxels,true);
}
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex) for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
{ {
const auto& passage=ResolvedLayout->passages[passageIndex]; const auto& passage=ResolvedLayout->passages[passageIndex];
if(passage.controlPoints.size()<2)continue; if(passage.controlPoints.size()<2)continue;
const auto supportStratum=[&](std::int32_t strateIndex)
->const VoxelForgeCore::ResolvedLayoutStratumV2*
{
const auto it=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[strateIndex](const auto& stratum)
{return stratum.strateIndex==strateIndex;});
return it==ResolvedLayout->strates.end()?nullptr:&*it;
};
const auto* supportUpper=supportStratum(passage.upperStrateIndex);
const auto* supportLower=supportStratum(passage.lowerStrateIndex);
const bool floatingIslandPassage=supportUpper
&&supportUpper->generatorKind=="FloatingIslands";
const bool surfaceToTunnelSupport=supportUpper&&supportLower
&&supportUpper->generatorKind=="SurfaceWorld"
&&supportLower->generatorKind=="TunnelNetwork";
const double firstFloorQ=passage.controlPoints.front().positionSubvoxels[2] const double firstFloorQ=passage.controlPoints.front().positionSubvoxels[2]
-passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ; -passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ;
const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2] const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2]
@@ -880,7 +916,23 @@ public:
std::ptrdiff_t bestScanIndex=std::numeric_limits<std::ptrdiff_t>::max(); std::ptrdiff_t bestScanIndex=std::numeric_limits<std::ptrdiff_t>::max();
std::ptrdiff_t bestSupportSegmentIndex=0; std::ptrdiff_t bestSupportSegmentIndex=0;
bool hasSupportFloor=false; bool hasSupportFloor=false;
double bestSupportDensity=0.0; bool bestSupportIsMouthTread=false;
double bestSupportMouthBlend=1.0;
double bestSupportDistanceSquared=std::numeric_limits<double>::infinity();
double bestSupportDensity=std::numeric_limits<double>::infinity();
double bestSupportNativeBlend=0.0;
double bestSupportFloorQ=0.0;
bool bestSupportUsesSignedDistance=false;
std::ptrdiff_t selectedSupportSegmentIndex=-1;
const double lowerMouthDx=qx-passage.lowerMouthSubvoxels[0];
const double lowerMouthDy=qy-passage.lowerMouthSubvoxels[1];
const double lowerMouthDz=qz-passage.lowerMouthSubvoxels[2];
const double lowerMouthDistance=DeterministicSqrt(
lowerMouthDx*lowerMouthDx+lowerMouthDy*lowerMouthDy
+lowerMouthDz*lowerMouthDz)/qPerMetre;
const double lowerMouthT=std::clamp((20.0-lowerMouthDistance)/12.0,0.0,1.0);
const double lowerMouthBlend=lowerMouthT*lowerMouthT
*(3.0-2.0*lowerMouthT);
const auto considerCarveSegment=[&](std::size_t segmentIndex,int scanPhase) const auto considerCarveSegment=[&](std::size_t segmentIndex,int scanPhase)
{ {
const std::ptrdiff_t index=static_cast<std::ptrdiff_t>(segmentIndex); const std::ptrdiff_t index=static_cast<std::ptrdiff_t>(segmentIndex);
@@ -992,50 +1044,217 @@ public:
/distanceToCenter,0.0,1.0):0.0; /distanceToCenter,0.0,1.0):0.0;
const float relief=VF_FieldPassageWallRelief(static_cast<float>(X),static_cast<float>(Y), const float relief=VF_FieldPassageWallRelief(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),static_cast<std::uint32_t>(Owner?Owner->Seed:0)); static_cast<float>(Z),static_cast<std::uint32_t>(Owner?Owner->Seed:0));
const double wallReliefWeight=1.0-0.80*verticalWallAlignment; const float mouthSurfaceRelief=VF_FieldSurfaceRelief(static_cast<float>(X)*1.5f,
static_cast<float>(Y)*1.5f,static_cast<float>(Z)*1.5f,
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u);
const float mouthTerrainRelief=VF_FieldSurfaceRelief(static_cast<float>(X)*1.5f,
static_cast<float>(Y)*1.5f,static_cast<float>(Z)*1.5f,
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u);
const float mouthTreadFieldRelief=VF_FieldSurfaceRelief(static_cast<float>(X)*0.75f,
static_cast<float>(Y)*0.75f,static_cast<float>(Z)*0.75f,
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x54485244u);
const auto routeSegmentLengthMetres=[&](std::ptrdiff_t routeIndex)
{
const auto& before=passage.controlPoints[static_cast<std::size_t>(routeIndex)];
const auto& after=passage.controlPoints[static_cast<std::size_t>(routeIndex+1)];
const double dx=static_cast<double>(after.positionSubvoxels[0])
-before.positionSubvoxels[0];
const double dy=static_cast<double>(after.positionSubvoxels[1])
-before.positionSubvoxels[1];
const double dz=static_cast<double>(after.positionSubvoxels[2])
-before.positionSubvoxels[2];
return DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre;
};
double distanceFromOpenMouth=std::numeric_limits<double>::infinity();
if(bestIndex<=2)
{
distanceFromOpenMouth=bestT*routeSegmentLengthMetres(bestIndex);
for(std::ptrdiff_t routeIndex=0;routeIndex<bestIndex;++routeIndex)
distanceFromOpenMouth+=routeSegmentLengthMetres(routeIndex);
}
else if(bestIndex>=lowerMouthIndex-2&&bestIndex<=lowerMouthIndex+2)
{
if(bestIndex<lowerMouthIndex)
{
distanceFromOpenMouth=(1.0-bestT)*routeSegmentLengthMetres(bestIndex);
for(std::ptrdiff_t routeIndex=bestIndex+1;
routeIndex<lowerMouthIndex;++routeIndex)
distanceFromOpenMouth+=routeSegmentLengthMetres(routeIndex);
}
else
{
distanceFromOpenMouth=bestT*routeSegmentLengthMetres(bestIndex);
for(std::ptrdiff_t routeIndex=lowerMouthIndex;routeIndex<bestIndex;++routeIndex)
distanceFromOpenMouth+=routeSegmentLengthMetres(routeIndex);
}
}
double mouthEntryFloorAlignment=0.0;
double mouthEntryPlanDistance=std::numeric_limits<double>::infinity();
double mouthEntryFloorQ=0.0;
bool upperMouthFloorFade=false;
{
// The large mouth bore overlaps several inner coils. Its global
// nearest SDF can belong to a later turn even at the lip, so use the
// projected entrance path to recover the local floor orientation.
double prefixMetres=0.0;
double nearestEntryPlanDistance=std::numeric_limits<double>::infinity();
double projectedEntryDistance=std::numeric_limits<double>::infinity();
for(std::ptrdiff_t routeIndex=0;
routeIndex<std::min<std::ptrdiff_t>(3,lowerMouthIndex);++routeIndex)
{
const auto& before=passage.controlPoints[static_cast<std::size_t>(routeIndex)];
const auto& after=passage.controlPoints[static_cast<std::size_t>(routeIndex+1)];
const double ax=static_cast<double>(before.positionSubvoxels[0]);
const double ay=static_cast<double>(before.positionSubvoxels[1]);
const double az=static_cast<double>(before.positionSubvoxels[2]);
const double dx=static_cast<double>(after.positionSubvoxels[0])-ax;
const double dy=static_cast<double>(after.positionSubvoxels[1])-ay;
const double dz=static_cast<double>(after.positionSubvoxels[2])-az;
const double horizontalLengthSq=dx*dx+dy*dy;
const double t=horizontalLengthSq>0.0?std::clamp(
((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):0.0;
double projectedX=ax+t*dx,projectedY=ay+t*dy;
const double horizontalLength=DeterministicSqrt(horizontalLengthSq);
if(horizontalLength>1.0e-6)
{
const double pathRelief=std::clamp(static_cast<double>(
VF_FieldSurfaceRelief(static_cast<float>(projectedX/qPerMetre*1.5),
static_cast<float>(projectedY/qPerMetre*1.5),
static_cast<float>((az+t*dz)/qPerMetre*1.5),
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u)),
-1.0,1.0);
const double lateralOffset=pathRelief*0.18*qPerMetre;
projectedX-=dy/horizontalLength*lateralOffset;
projectedY+=dx/horizontalLength*lateralOffset;
}
const double planDx=qx-projectedX,planDy=qy-projectedY;
const double planDistance=DeterministicSqrt(planDx*planDx+planDy*planDy);
if(planDistance<nearestEntryPlanDistance)
{
nearestEntryPlanDistance=planDistance;
const double centerZ=az+t*dz;
const double radius=before.radiusSubvoxels+t*(
static_cast<double>(after.radiusSubvoxels)-before.radiusSubvoxels);
mouthEntryFloorQ=centerZ-radius+0.20*qPerMetre;
const double centerDistance=DeterministicSqrt(planDx*planDx
+planDy*planDy+(qz-centerZ)*(qz-centerZ));
mouthEntryFloorAlignment=centerDistance>1.0e-9
?std::clamp(-(qz-centerZ)/centerDistance,0.0,1.0):0.0;
projectedEntryDistance=prefixMetres
+t*routeSegmentLengthMetres(routeIndex);
}
prefixMetres+=routeSegmentLengthMetres(routeIndex);
}
if(nearestEntryPlanDistance<=2.0*qPerMetre
&&projectedEntryDistance<=8.0
&&std::abs(qz-mouthEntryFloorQ)<=4.0*qPerMetre)
{
distanceFromOpenMouth=projectedEntryDistance;
mouthEntryPlanDistance=nearestEntryPlanDistance/qPerMetre;
upperMouthFloorFade=true;
}
}
double mouthFloorBlend=0.0;
if(std::isfinite(distanceFromOpenMouth))
{
const double fade=std::clamp(distanceFromOpenMouth/11.0,0.0,1.0);
const double smooth=fade*fade*(3.0-2.0*fade);
mouthFloorBlend=1.0-smooth;
}
const double floorReliefT=std::clamp((verticalWallAlignment-0.55)/0.45,0.0,1.0);
const double floorReliefWeight=floorReliefT*floorReliefT
*(3.0-2.0*floorReliefT);
const double floorReliefOffset=std::clamp(
static_cast<double>(mouthTerrainRelief),-1.0,1.0)
*0.20*qPerMetre*mouthFloorBlend*floorReliefWeight
*(upperMouthFloorFade?1.0:0.0);
const double wallReliefWeight=0.05+0.95*(1.0-verticalWallAlignment);
const double roughSignedDistance=bestSignedDistance const double roughSignedDistance=bestSignedDistance
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.21*qPerMetre*wallReliefWeight; +static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.21*qPerMetre*wallReliefWeight
+floorReliefOffset;
if(IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(bestIndex))) if(IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(bestIndex)))
{ {
const double bridgeDistance=PassageBridgeSignedDistance(passageIndex, const double bridgeDistance=PassageBridgeSignedDistance(passageIndex,
static_cast<std::size_t>(bestIndex),X,Y,Z); static_cast<std::size_t>(bestIndex),X,Y,Z);
composed=std::min(composed,static_cast<float>(bridgeDistance/Q)); composed=std::min(composed,static_cast<float>(bridgeDistance/Q));
} }
// Positive density is air; rough rock is cut with full side/ceiling detail and // Leave native crater ground at the lip. The bore opens through the walls
// gentler relief on the gravity-facing wall so the supported tread stays open. // immediately, while its floor cut eases in along the passage centerline.
composed=std::max(composed,static_cast<float>(-roughSignedDistance/Q)); double mouthCarveFade=1.0;
double bestSupportDistanceSquared=std::numeric_limits<double>::infinity(); if(upperMouthFloorFade)
{
constexpr double mouthCarveFadeMetres=8.0;
mouthCarveFade=std::clamp(distanceFromOpenMouth/mouthCarveFadeMetres,
0.0,1.0);
mouthCarveFade=mouthCarveFade*mouthCarveFade*(3.0-2.0*mouthCarveFade);
}
const double floorAlignment=std::max(verticalWallAlignment,
mouthEntryFloorAlignment)*std::max(verticalWallAlignment,
mouthEntryFloorAlignment);
double floorBelowWeight=0.0;
if(upperMouthFloorFade)
{
const double belowFloor=(mouthEntryFloorQ-qz)/qPerMetre;
const double supportRamp=std::clamp(belowFloor/0.15,0.0,1.0);
const double supportFade=std::clamp((1.0-belowFloor)/0.35,0.0,1.0);
const double rampSmooth=supportRamp*supportRamp*(3.0-2.0*supportRamp);
const double fadeSmooth=supportFade*supportFade*(3.0-2.0*supportFade);
floorBelowWeight=rampSmooth*fadeSmooth;
}
// Preserve native terrain only along a capsule-scale worn lane. The
// former bore-wide alignment feather left a straight platform-width
// bridge across the FI mouth.
double mouthWalkLaneT=std::clamp((1.05-mouthEntryPlanDistance)/0.60,0.0,1.0);
mouthWalkLaneT=mouthWalkLaneT*mouthWalkLaneT*(3.0-2.0*mouthWalkLaneT);
const double mouthWalkLaneWeight=upperMouthFloorFade?mouthWalkLaneT:0.0;
const double floorCarveWeight=1.0-(1.0-mouthCarveFade)
*floorAlignment*floorAlignment*floorBelowWeight*mouthWalkLaneWeight;
const float fullyCarvedDensity=std::max(composed,
static_cast<float>(-roughSignedDistance/Q));
composed=std::lerp(composed,fullyCarvedDensity,
static_cast<float>(floorCarveWeight));
const auto considerSupportSegment=[&](std::ptrdiff_t index) const auto considerSupportSegment=[&](std::ptrdiff_t index)
{ {
const auto& segmentA=passage.controlPoints[static_cast<std::size_t>(index)]; const auto& segmentA=passage.controlPoints[static_cast<std::size_t>(index)];
const auto& segmentB=passage.controlPoints[static_cast<std::size_t>(index+1)]; const auto& segmentB=passage.controlPoints[static_cast<std::size_t>(index+1)];
const double dx=static_cast<double>(segmentB.positionSubvoxels[0])-segmentA.positionSubvoxels[0]; const double dx=static_cast<double>(segmentB.positionSubvoxels[0])-segmentA.positionSubvoxels[0];
const double dy=static_cast<double>(segmentB.positionSubvoxels[1])-segmentA.positionSubvoxels[1]; const double dy=static_cast<double>(segmentB.positionSubvoxels[1])-segmentA.positionSubvoxels[1];
const double dz=static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2];
const double radiusA=segmentA.radiusSubvoxels; const double radiusA=segmentA.radiusSubvoxels;
const double radiusB=segmentB.radiusSubvoxels; const double radiusB=segmentB.radiusSubvoxels;
const bool entryMouthShoulder=index<=2; const bool entryMouthShoulder=index<=2;
// Keep the tread within the probe band and below the capsule's // Keep the tread within the probe band and below the capsule's
// lowest sampled point. // lowest sampled point.
const double floorOffset=IsPassageShortSpanSegment(passageIndex, const bool bridgeDeck=IsPassageShortSpanSegment(passageIndex,
static_cast<std::size_t>(index)) static_cast<std::size_t>(index))
||IsPassageColumnSegment(passageIndex,static_cast<std::size_t>(index)) ||IsPassageColumnSegment(passageIndex,static_cast<std::size_t>(index));
// Keep a small capsule margin at regular treads and restore most of
// the nominal height smoothly as a tread approaches its landing.
double floorOffset=bridgeDeck
?PassageBridgeDeckOffsetMetres(passageIndex,static_cast<std::size_t>(index)) ?PassageBridgeDeckOffsetMetres(passageIndex,static_cast<std::size_t>(index))
:0.20; :std::lerp(0.15,0.20,lowerMouthBlend);
const double horizontalLengthSq=dx*dx+dy*dy; const double horizontalLengthSq=dx*dx+dy*dy;
const bool overlapSupportJoin=index+12<lastIndex
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index))
&&(index==0||(!IsPassageShortSpanSegment(passageIndex,
static_cast<std::size_t>(index-1))));
const bool smoothSupportJoin=index>0&&overlapSupportJoin
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index+1));
const double projection=horizontalLengthSq>0.0
?((qx-segmentA.positionSubvoxels[0])*dx
+(qy-segmentA.positionSubvoxels[1])*dy)/horizontalLengthSq:bestT;
// Extend tread segments by a fixed distance through route knots. A
// fractional extension made the support width change with segment length.
const double overlapFraction=overlapSupportJoin&&horizontalLengthSq>0.0
?std::min(0.5,2.0*qPerMetre/DeterministicSqrt(horizontalLengthSq)):0.0;
const double supportT=horizontalLengthSq>0.0 const double supportT=horizontalLengthSq>0.0
?std::clamp(((qx-segmentA.positionSubvoxels[0])*dx ?std::clamp(projection,-overlapFraction,1.0+overlapFraction):bestT;
+(qy-segmentA.positionSubvoxels[1])*dy)/horizontalLengthSq,0.0,1.0) double centerX=segmentA.positionSubvoxels[0]+supportT*dx;
:bestT; double centerY=segmentA.positionSubvoxels[1]+supportT*dy;
const double centerX=segmentA.positionSubvoxels[0]+supportT*dx;
const double centerY=segmentA.positionSubvoxels[1]+supportT*dy;
double centerZ=segmentA.positionSubvoxels[2]+supportT*( double centerZ=segmentA.positionSubvoxels[2]+supportT*(
static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]); static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]);
double radius=segmentA.radiusSubvoxels+supportT*( double radius=segmentA.radiusSubvoxels+supportT*(
static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels); static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels);
const bool smoothSupportJoin=index>0&&index+12<lastIndex
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index-1))
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index))
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index+1));
if(smoothSupportJoin) if(smoothSupportJoin)
{ {
const auto& p0=passage.controlPoints[static_cast<std::size_t>(index-1)]; const auto& p0=passage.controlPoints[static_cast<std::size_t>(index-1)];
@@ -1055,6 +1274,25 @@ public:
segmentB.radiusSubvoxels,p3.radiusSubvoxels)-radius) segmentB.radiusSubvoxels,p3.radiusSubvoxels)-radius)
*SmoothSupportHeightWeight; *SmoothSupportHeightWeight;
} }
double treadRelief=0.0;
if(entryMouthShoulder
||(index>=lowerMouthIndex-2&&index<=lowerMouthIndex+2))
{
const double horizontalLength=DeterministicSqrt(horizontalLengthSq);
if(horizontalLength>1.0e-6)
{
const double pathRelief=std::clamp(static_cast<double>(
VF_FieldSurfaceRelief(static_cast<float>(centerX/qPerMetre*1.5),
static_cast<float>(centerY/qPerMetre*1.5),
static_cast<float>(centerZ/qPerMetre*1.5),
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u)),
-1.0,1.0);
treadRelief=pathRelief;
const double lateralOffset=pathRelief*0.18*qPerMetre;
centerX-=dy/horizontalLength*lateralOffset;
centerY+=dx/horizontalLength*lateralOffset;
}
}
const double horizontalX=qx-centerX,horizontalY=qy-centerY; const double horizontalX=qx-centerX,horizontalY=qy-centerY;
const double horizontalDistance=DeterministicSqrt(horizontalX*horizontalX const double horizontalDistance=DeterministicSqrt(horizontalX*horizontalX
+horizontalY*horizontalY); +horizontalY*horizontalY);
@@ -1063,7 +1301,17 @@ public:
// Support belongs just below the avatar's foot plane. Queries within // Support belongs just below the avatar's foot plane. Queries within
// the capsule body, or a neighbouring coil's overlapping bore, must // the capsule body, or a neighbouring coil's overlapping bore, must
// not turn that tread into a ceiling or mid-air shelf. // not turn that tread into a ceiling or mid-air shelf.
if(supportQueryDepth<0.93*qPerMetre||supportQueryDepth>1.30*qPerMetre)return; // Sparse authored routes use the full signed distance around the top;
// dense routes retain the legacy one-sided tread density.
const double supportSegmentLength=DeterministicSqrt(
horizontalLengthSq+dz*dz)/qPerMetre;
const double sparseRouteThreshold=VoxelForgeCore::WorldAssemblyEvaluatorVersion>=188
&&floatingIslandPassage&&passage.style==0?1.0:1.5;
const bool sparseRouteSupport=supportSegmentLength>sparseRouteThreshold;
const double minimumSupportQueryDepth=index==selectedSupportSegmentIndex
&&sparseRouteSupport?0.65:0.93;
if(supportQueryDepth<minimumSupportQueryDepth*qPerMetre
||supportQueryDepth>1.30*qPerMetre)return;
const bool fallbackSupport=qz>=supportPlayerCenterZ-1.50*qPerMetre; const bool fallbackSupport=qz>=supportPlayerCenterZ-1.50*qPerMetre;
const double supportThicknessVoxels=fallbackSupport const double supportThicknessVoxels=fallbackSupport
?qPerMetre/Q:0.5*qPerMetre/Q; ?qPerMetre/Q:0.5*qPerMetre/Q;
@@ -1072,19 +1320,75 @@ public:
// capsule on the neighbouring turn. // capsule on the neighbouring turn.
const bool lowerMouthBodyOverlap=index>=lowerMouthIndex-2 const bool lowerMouthBodyOverlap=index>=lowerMouthIndex-2
&&supportQueryDepth<0.98*qPerMetre; &&supportQueryDepth<0.98*qPerMetre;
const double supportEdgeNoise=entryMouthShoulder?std::clamp( // Keep a narrow walk core, but place its top on the native surface
static_cast<double>(VF_FieldPassageGroundGrain(static_cast<float>(X), // so the support remains a worn ramp instead of a route-height slab.
static_cast<float>(Y), const bool mouthTread=entryMouthShoulder
static_cast<std::uint32_t>(Owner?Owner->Seed:0))),-1.0,1.0):0.0; &&(!floatingIslandPassage||passage.style==0);
const double supportHalfWidthMetres=lowerMouthBodyOverlap?0.90 double distanceFromMouthMetres=std::numeric_limits<double>::infinity();
:entryMouthShoulder?1.20+0.20*supportEdgeNoise:1.25; if(entryMouthShoulder)
const double supportHalfWidth=std::min(radius, {
double prefixMetres=0.0;
for(std::ptrdiff_t routeIndex=0;routeIndex<index;++routeIndex)
{
const auto& before=passage.controlPoints[
static_cast<std::size_t>(routeIndex)];
const auto& after=passage.controlPoints[
static_cast<std::size_t>(routeIndex+1)];
const double dx=static_cast<double>(after.positionSubvoxels[0])
-before.positionSubvoxels[0];
const double dy=static_cast<double>(after.positionSubvoxels[1])
-before.positionSubvoxels[1];
const double dz=static_cast<double>(after.positionSubvoxels[2])
-before.positionSubvoxels[2];
prefixMetres+=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre;
}
distanceFromMouthMetres=std::max(0.0,
prefixMetres+supportT*supportSegmentLength);
}
else if(lowerMouthBodyOverlap)
{
double suffixMetres=0.0;
for(std::ptrdiff_t routeIndex=index+1;
routeIndex<lowerMouthIndex;++routeIndex)
{
const auto& before=passage.controlPoints[
static_cast<std::size_t>(routeIndex)];
const auto& after=passage.controlPoints[
static_cast<std::size_t>(routeIndex+1)];
const double dx=static_cast<double>(after.positionSubvoxels[0])
-before.positionSubvoxels[0];
const double dy=static_cast<double>(after.positionSubvoxels[1])
-before.positionSubvoxels[1];
const double dz=static_cast<double>(after.positionSubvoxels[2])
-before.positionSubvoxels[2];
suffixMetres+=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre;
}
distanceFromMouthMetres=std::max(0.0,
suffixMetres+(1.0-supportT)*supportSegmentLength);
}
double mouthReliefT=std::clamp(distanceFromMouthMetres/8.0,0.0,1.0);
mouthReliefT=mouthReliefT*mouthReliefT*(3.0-2.0*mouthReliefT);
double mouthSupportT=std::clamp((distanceFromMouthMetres-0.25)/4.0,0.0,1.0);
mouthSupportT=mouthSupportT*mouthSupportT*(3.0-2.0*mouthSupportT);
const double mouthSupportWidth=std::lerp(0.82,1.00,mouthSupportT);
const double supportHalfWidthMetres=mouthTread?mouthSupportWidth
:lowerMouthBodyOverlap?0.90:1.25;
double supportHalfWidth=std::min(radius,
supportHalfWidthMetres*qPerMetre); supportHalfWidthMetres*qPerMetre);
// Keep the walk core within a 0.50 m span with shallow relief, then const double shoulderWarp=mouthTread
// taper the outer shoulder; capsule and probe widths remain supported. ?std::clamp(static_cast<double>(mouthSurfaceRelief),-1.0,1.0)
const double supportEdgeCoreMetres=entryMouthShoulder?0.60:0.90; *0.12*qPerMetre:0.0;
supportHalfWidth=mouthTread
?std::clamp(supportHalfWidth+shoulderWarp,
std::lerp(0.52,0.62,mouthSupportT)*qPerMetre,
std::lerp(0.92,1.10,mouthSupportT)*qPerMetre)
:std::max(0.0,supportHalfWidth+shoulderWarp);
// Keep the walk core within the player footprint, then ease the outer
// edge into native ground over a long enough shoulder to avoid a berm.
const double supportEdgeCoreMetres=mouthTread?0.08
:entryMouthShoulder?0.60:0.90;
const double supportEdgeBlendMetres=std::min( const double supportEdgeBlendMetres=std::min(
entryMouthShoulder?0.85:0.35, mouthTread?1.40:entryMouthShoulder?0.85:0.35,
std::max(0.0,supportHalfWidth/qPerMetre-supportEdgeCoreMetres)); std::max(0.0,supportHalfWidth/qPerMetre-supportEdgeCoreMetres));
const double supportEdgeT=supportEdgeBlendMetres>1.0e-6 const double supportEdgeT=supportEdgeBlendMetres>1.0e-6
?std::clamp((horizontalDistance ?std::clamp((horizontalDistance
@@ -1092,33 +1396,87 @@ public:
/(supportEdgeBlendMetres*qPerMetre),0.0,1.0):0.0; /(supportEdgeBlendMetres*qPerMetre),0.0,1.0):0.0;
const double supportEdgeSmooth=supportEdgeT*supportEdgeT const double supportEdgeSmooth=supportEdgeT*supportEdgeT
*(3.0-2.0*supportEdgeT); *(3.0-2.0*supportEdgeT);
const double mouthSupportThicknessVoxels=std::lerp(
0.35*supportThicknessVoxels,supportThicknessVoxels,mouthSupportT);
const double localSupportThicknessVoxels=entryMouthShoulder const double localSupportThicknessVoxels=entryMouthShoulder
?supportThicknessVoxels*(1.0-0.72*supportEdgeSmooth) ?mouthSupportThicknessVoxels*(1.0-0.72*supportEdgeSmooth)
:supportThicknessVoxels; :supportThicknessVoxels;
const double supportEdgeDepth=entryMouthShoulder double mouthSupportBlend=1.0;
?0.35+0.15*supportEdgeNoise:0.35; if(mouthTread&&std::isfinite(distanceFromMouthMetres))
const double floorQ=centerZ-radius+floorOffset*qPerMetre {
+supportEdgeNoise*(horizontalDistance>0.60*qPerMetre const double mouthBlendT=std::clamp(
?0.35:0.14)*qPerMetre distanceFromMouthMetres/0.75,0.0,1.0);
mouthSupportBlend=0.75+0.25*mouthBlendT*mouthBlendT
*(3.0-2.0*mouthBlendT);
}
const double supportEdgeDepth=mouthTread?0.25:0.35;
const double mouthFloorOffset=mouthTread
?std::lerp(0.15,0.19,lowerMouthBlend):floorOffset;
const double baseFloorQ=centerZ-radius+mouthFloorOffset*qPerMetre
-supportEdgeSmooth*supportEdgeDepth*qPerMetre; -supportEdgeSmooth*supportEdgeDepth*qPerMetre;
const double nativeSurfaceQ=static_cast<double>(qz)
-static_cast<double>(density)*Q;
// Keep support near the route floor; visible mouth preservation below
// blends the source field directly instead of extending this SDF top.
const double nativeFieldOffset=std::clamp(nativeSurfaceQ-baseFloorQ,
-0.80*qPerMetre,0.80*qPerMetre);
const double nativeSurfaceBlend=mouthTread?1.0:1.0-mouthReliefT;
const double nativeTreadOffset=std::clamp(
nativeFieldOffset*nativeSurfaceBlend+floorReliefOffset
-std::abs(treadRelief)*0.08*qPerMetre
*(1.0-0.25*mouthReliefT),
-0.80*qPerMetre,0.80*qPerMetre);
const double floorQ=baseFloorQ
+(mouthTread?std::clamp(nativeTreadOffset
+std::clamp(static_cast<double>(mouthTreadFieldRelief),-1.0,1.0)
*0.40*qPerMetre,-0.80*qPerMetre,0.0):0.0);
const double floorVoxels=floorQ/Q; const double floorVoxels=floorQ/Q;
if(Z<floorVoxels-localSupportThicknessVoxels||Z>floorVoxels+1.0/Q)return; const bool signedDistanceSupport=sparseRouteSupport||mouthTread;
if(horizontalDistance>supportHalfWidth)return; const double supportPositiveRangeVoxels=signedDistanceSupport
const double supportDistanceSq=horizontalDistance*horizontalDistance ?0.35*qPerMetre/Q:1.0/Q;
if(Z<floorVoxels-localSupportThicknessVoxels
||Z>floorVoxels+supportPositiveRangeVoxels)return;
const double supportQueryHalfWidth=supportHalfWidth
+(mouthTread?0.45*qPerMetre:0.0);
if(horizontalDistance>supportQueryHalfWidth)return;
const double supportDistanceSquared=horizontalDistance*horizontalDistance
+(Z-floorVoxels)*(Z-floorVoxels); +(Z-floorVoxels)*(Z-floorVoxels);
if(supportDistanceSq>=bestSupportDistanceSquared)return; if(supportDistanceSquared>=bestSupportDistanceSquared)return;
bestSupportDistanceSquared=supportDistanceSq; bestSupportDistanceSquared=supportDistanceSquared;
hasSupportFloor=true; hasSupportFloor=true;
bestSupportSegmentIndex=index; bestSupportSegmentIndex=index;
bestSupportIsMouthTread=mouthTread;
if(signedDistanceSupport)
{
// Sparse support profiles need both sides of a signed distance
// so the sampled zero crossing stays on the tread top.
const double topDistance=qz-floorQ;
const double bottomDistance=floorQ-localSupportThicknessVoxels*Q-qz;
const double verticalDistance=std::max(topDistance,bottomDistance);
const double sideDistance=horizontalDistance-supportHalfWidth;
bestSupportDensity=SmoothMaxDistance(
static_cast<float>(sideDistance/Q),
static_cast<float>(verticalDistance/Q),
entryMouthShoulder?0.80f:0.20f);
}
else
{
bestSupportDensity=std::clamp(Z-floorVoxels, bestSupportDensity=std::clamp(Z-floorVoxels,
-localSupportThicknessVoxels,0.0); -localSupportThicknessVoxels,0.0);
}
bestSupportFloorQ=floorQ;
bestSupportUsesSignedDistance=signedDistanceSupport;
bestSupportMouthBlend=mouthSupportBlend;
bestSupportNativeBlend=mouthTread
?0.85*mouthSupportBlend*(1.0-supportEdgeSmooth):0.0;
}; };
std::ptrdiff_t supportCenterIndex=bestIndex; std::ptrdiff_t supportCenterIndex=bestIndex;
if(bestIndex<lowerMouthIndex) if(bestIndex<lowerMouthIndex)
{ {
// A helix can have multiple centerlines at one XY. Select the tread // A helix can have multiple centerlines at one XY. Select the tread
// whose floor is just below this density query, not the closest wall. // whose floor is just below this density query, not the closest wall.
const double supportProfileTarget=qz+0.93*qPerMetre; const double supportProfileOffset=surfaceToTunnelSupport?1.13:0.93;
const double supportProfileTarget=qz+supportProfileOffset*qPerMetre;
const auto supportProfile=std::lower_bound(passage.controlPoints.begin(),mainEnd, const auto supportProfile=std::lower_bound(passage.controlPoints.begin(),mainEnd,
supportProfileTarget,[&](const auto& point,double value) supportProfileTarget,[&](const auto& point,double value)
{return floorProfile(point)>value;}); {return floorProfile(point)>value;});
@@ -1132,6 +1490,8 @@ public:
const std::ptrdiff_t supportBegin=std::max<std::ptrdiff_t>(0,supportCenterIndex-1); const std::ptrdiff_t supportBegin=std::max<std::ptrdiff_t>(0,supportCenterIndex-1);
const std::ptrdiff_t supportEnd=std::min<std::ptrdiff_t>(lastIndex-1, const std::ptrdiff_t supportEnd=std::min<std::ptrdiff_t>(lastIndex-1,
supportCenterIndex+1); supportCenterIndex+1);
selectedSupportSegmentIndex=supportCenterIndex>0
?supportCenterIndex-1:supportCenterIndex;
for(std::ptrdiff_t index=supportBegin;index<=supportEnd;++index) for(std::ptrdiff_t index=supportBegin;index<=supportEnd;++index)
considerSupportSegment(index); considerSupportSegment(index);
// Keep route treads inside the native rock envelope. Carving may reopen the // Keep route treads inside the native rock envelope. Carving may reopen the
@@ -1142,13 +1502,58 @@ public:
const bool shortSpanFloorSupport=hasSupportFloor const bool shortSpanFloorSupport=hasSupportFloor
&&IsPassageShortSpanSegment(passageIndex, &&IsPassageShortSpanSegment(passageIndex,
static_cast<std::size_t>(bestSupportSegmentIndex)); static_cast<std::size_t>(bestSupportSegmentIndex));
const bool capsuleAllowsSupport=passageTileScopeMatches const double capsuleClearanceMetres=bestSupportUsesSignedDistance?0.045:0.05;
const double capsuleQueryZ=bestSupportUsesSignedDistance?bestSupportFloorQ:qz;
const bool supportCapsuleProfileAvailable=passageIndex<PassageNearestNodes.size()
&&!PassageNearestNodes[passageIndex].empty();
const bool capsuleAllowsSupport=(passageTileScopeMatches
||(bestSupportUsesSignedDistance&&supportCapsuleProfileAvailable))
?!PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin,carveEnd, ?!PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin,carveEnd,
lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz,qPerMetre) lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,
:bestPlayerCapsuleSignedDistance>=0.05*qPerMetre; capsuleQueryZ,qPerMetre,capsuleClearanceMetres)
if(hasSupportFloor&&(density<0.0f||passageColumnFloorSupport||shortSpanFloorSupport) :bestPlayerCapsuleSignedDistance>=capsuleClearanceMetres*qPerMetre;
&&capsuleAllowsSupport) // Outside bridge and short-span segments, add the tread only where the
composed=std::min(composed,static_cast<float>(bestSupportDensity)); // native field already provides a solid envelope; the capsule check keeps
// that support below the player's walk space.
if(hasSupportFloor&&(density<0.0f||passageColumnFloorSupport
||shortSpanFloorSupport)
&&(capsuleAllowsSupport||bestSupportDensity>=0.0))
{
if(bestSupportIsMouthTread)
{
// Preserve the owner's native ground through the shallow mouth
// carve. Blending back the native field keeps its crater curvature
// and noise. The capsule test keeps neighbouring coils open.
const bool nativeRestoreCapsuleClear=density<0.0f
&&bestSupportNativeBlend>0.0&&capsuleAllowsSupport
&&!PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin,
carveEnd,lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,
lastIndex,qx,qy,qz,qPerMetre,capsuleClearanceMetres);
if(nativeRestoreCapsuleClear)
{
const double nativeSurfaceDetail=std::clamp(
static_cast<double>(mouthTerrainRelief),-1.0,1.0)
*0.18*qPerMetre/Q;
const float nativeGroundWithRelief=static_cast<float>(density
-nativeSurfaceDetail);
composed=std::lerp(composed,nativeGroundWithRelief,
static_cast<float>(bestSupportNativeBlend));
}
}
else composed=std::min(composed,static_cast<float>(bestSupportDensity));
}
}
// Restore the surface route where the passage cut crossed otherwise solid
// native ground. The support stays native-solid-only, so it cannot float.
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
{
const auto& passage=ResolvedLayout->passages[passageIndex];
if(passage.controlPoints.size()<2)continue;
const bool tileHasSupport=passageTileScopeMatches;
if(!tileHasSupport
||ActivePassageTileQueries->hasMouthSupport(passageIndex)))
composeRouteMouthSupport(passage.upperStrateIndex,
passage.upperMouthSubvoxels,true);
} }
return composed; return composed;
} }
@@ -2990,7 +3395,8 @@ private:
} }
bool PassagePlayerCapsuleBlocksSupportNode(std::size_t passageIndex,int nodeIndex, bool PassagePlayerCapsuleBlocksSupportNode(std::size_t passageIndex,int nodeIndex,
std::size_t first,std::size_t end,double qx,double qy,double qz,double qPerMetre) const std::size_t first,std::size_t end,double qx,double qy,double qz,double qPerMetre,
double minimumClearanceMetres) const
{ {
if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return false; if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return false;
const auto& nodes=PassageNearestNodes[passageIndex]; const auto& nodes=PassageNearestNodes[passageIndex];
@@ -3026,26 +3432,26 @@ private:
const double vertical=std::max(0.0,std::abs(qz-centerZ)-0.54*qPerMetre); const double vertical=std::max(0.0,std::abs(qz-centerZ)-0.54*qPerMetre);
const double distance=DeterministicSqrt(horizontalX*horizontalX+horizontalY*horizontalY const double distance=DeterministicSqrt(horizontalX*horizontalX+horizontalY*horizontalY
+vertical*vertical)-0.34*qPerMetre; +vertical*vertical)-0.34*qPerMetre;
if(distance<0.05*qPerMetre)return true; if(distance<minimumClearanceMetres*qPerMetre)return true;
} }
return false; return false;
} }
return PassagePlayerCapsuleBlocksSupportNode(passageIndex,node.left,first,end, return PassagePlayerCapsuleBlocksSupportNode(passageIndex,node.left,first,end,
qx,qy,qz,qPerMetre) qx,qy,qz,qPerMetre,minimumClearanceMetres)
||PassagePlayerCapsuleBlocksSupportNode(passageIndex,node.right,first,end, ||PassagePlayerCapsuleBlocksSupportNode(passageIndex,node.right,first,end,
qx,qy,qz,qPerMetre); qx,qy,qz,qPerMetre,minimumClearanceMetres);
} }
bool PassagePlayerCapsuleBlocksSupport(std::size_t passageIndex, bool PassagePlayerCapsuleBlocksSupport(std::size_t passageIndex,
std::ptrdiff_t carveBegin,std::ptrdiff_t carveEnd,std::ptrdiff_t lowerMouthIndex, std::ptrdiff_t carveBegin,std::ptrdiff_t carveEnd,std::ptrdiff_t lowerMouthIndex,
std::ptrdiff_t mouthSearchBegin,std::ptrdiff_t mouthSearchEnd,std::ptrdiff_t lastIndex, std::ptrdiff_t mouthSearchBegin,std::ptrdiff_t mouthSearchEnd,std::ptrdiff_t lastIndex,
double qx,double qy,double qz,double qPerMetre) const double qx,double qy,double qz,double qPerMetre,double minimumClearanceMetres) const
{ {
const auto blocked=[&](std::ptrdiff_t first,std::ptrdiff_t last) const auto blocked=[&](std::ptrdiff_t first,std::ptrdiff_t last)
{ {
return first<=last&&PassagePlayerCapsuleBlocksSupportNode(passageIndex,0, return first<=last&&PassagePlayerCapsuleBlocksSupportNode(passageIndex,0,
static_cast<std::size_t>(first),static_cast<std::size_t>(last+1), static_cast<std::size_t>(first),static_cast<std::size_t>(last+1),
qx,qy,qz,qPerMetre); qx,qy,qz,qPerMetre,minimumClearanceMetres);
}; };
return blocked(carveBegin,carveEnd) return blocked(carveBegin,carveEnd)
||blocked(0,std::min<std::ptrdiff_t>(8,lowerMouthIndex)-1) ||blocked(0,std::min<std::ptrdiff_t>(8,lowerMouthIndex)-1)
@@ -4429,7 +4835,183 @@ private:
const VoxelForgeCore::FloatingIslandFieldV1 Field( const VoxelForgeCore::FloatingIslandFieldV1 Field(
Plan->manifest.recipe,Plan->manifest.plan); Plan->manifest.recipe,Plan->manifest.plan);
if(!Field.isValid())return false; if(!Field.isValid())return false;
std::int64_t Density=Field.densitySubvoxels(ToFixed(X),ToFixed(Y),ToFixed(Z)); const std::int64_t qx=ToFixed(X),qy=ToFixed(Y),qz=ToFixed(Z);
std::int64_t Density=Field.densitySubvoxels(qx,qy,qz);
const std::int64_t NativeDensity=Density;
// The anchor-root field keeps broad lobes, but its shortest authored noise
// wavelength is below one metre. Filter that detail only around a FloatingIslands
// passage mouth so Marching Cubes does not expose voxel-sized chips on the crater
// wall. The broad root profile is unchanged outside this local mouth envelope.
double mouthFilterWeight=0.0;
const double qToMetres=static_cast<double>(ResolvedLayout->coreVoxelSizeMicrometres)
/(static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)*1000000.0);
const double densityMetres=std::abs(static_cast<double>(Density))*qToMetres;
if(densityMetres<4.0)
{
bool hasResolvedPassageMouth=false;
for(const auto& passage:ResolvedLayout->passages)
{
if(passage.upperStrateIndex!=StrateIndex)continue;
hasResolvedPassageMouth=true;
const double dx=(static_cast<double>(qx)-passage.upperMouthSubvoxels[0])*qToMetres;
const double dy=(static_cast<double>(qy)-passage.upperMouthSubvoxels[1])*qToMetres;
const double dz=(static_cast<double>(qz)-passage.upperMouthSubvoxels[2])*qToMetres;
const double distance=std::sqrt(dx*dx+dy*dy+dz*dz);
constexpr double FilterCoreRadiusMetres=8.5;
constexpr double FilterOuterRadiusMetres=12.5;
const double fade=std::clamp((FilterOuterRadiusMetres-distance)
/(FilterOuterRadiusMetres-FilterCoreRadiusMetres),0.0,1.0);
const double weight=fade*fade*(3.0-2.0*fade);
mouthFilterWeight=std::max(mouthFilterWeight,weight);
}
if(!hasResolvedPassageMouth)
{
// The passage-free native control retains the stratum route anchors.
// Use its terminal route point as the same local mouth filter origin so
// both native and composed fields receive the same root smoothing.
const auto stratum=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[StrateIndex](const auto& value)
{return value.strateIndex==StrateIndex;});
if(stratum!=ResolvedLayout->strates.end())
{
const VoxelForgeCore::ResolvedLayoutAnchorV2* terminal=nullptr;
for(const auto& anchor:stratum->anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
terminal=&anchor;
if(terminal)
{
const double dx=static_cast<double>(qx-terminal->values[0])*qToMetres;
const double dy=static_cast<double>(qy-terminal->values[1])*qToMetres;
const double dz=static_cast<double>(qz-terminal->values[2])*qToMetres;
const double distance=std::sqrt(dx*dx+dy*dy+dz*dz);
constexpr double FilterCoreRadiusMetres=8.5;
constexpr double FilterOuterRadiusMetres=12.5;
const double fade=std::clamp((FilterOuterRadiusMetres-distance)
/(FilterOuterRadiusMetres-FilterCoreRadiusMetres),0.0,1.0);
mouthFilterWeight=fade*fade*(3.0-2.0*fade);
}
}
}
}
double routeNativeBlend=0.0;
const auto mouthPassage=std::find_if(ResolvedLayout->passages.begin(),
ResolvedLayout->passages.end(),[StrateIndex](const auto& passage)
{return passage.upperStrateIndex==StrateIndex;});
const double queryX=static_cast<double>(qx)*qToMetres;
const double queryY=static_cast<double>(qy)*qToMetres;
const double queryZ=static_cast<double>(qz)*qToMetres;
const bool outsideCraterMouth=mouthPassage==ResolvedLayout->passages.end()
||std::hypot(queryX-mouthPassage->upperMouthSubvoxels[0]*qToMetres,
queryY-mouthPassage->upperMouthSubvoxels[1]*qToMetres)>5.5;
if(mouthFilterWeight>0.0&&outsideCraterMouth)
{
// Retain the native route only on the outer approach. The crater-mouth
// interior stays with the filtered ground field and cannot become a bridge.
const auto stratum=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[StrateIndex](const auto& value)
{return value.strateIndex==StrateIndex;});
if(stratum!=ResolvedLayout->strates.end())
{
const VoxelForgeCore::ResolvedLayoutAnchorV2* previous=nullptr;
for(const auto& anchor:stratum->anchors)
{
if(anchor.kind!="route_point"||anchor.values.size()<3)continue;
if(previous)
{
const double ax=static_cast<double>(previous->values[0])*qToMetres;
const double ay=static_cast<double>(previous->values[1])*qToMetres;
const double az=static_cast<double>(previous->values[2])*qToMetres;
const double dx=(static_cast<double>(anchor.values[0])
-previous->values[0])*qToMetres;
const double dy=(static_cast<double>(anchor.values[1])
-previous->values[1])*qToMetres;
const double lengthSquared=dx*dx+dy*dy;
const double t=lengthSquared>0.0?std::clamp(
((queryX-ax)*dx+(queryY-ay)*dy)/lengthSquared,0.0,1.0):0.0;
const double centerX=ax+t*dx,centerY=ay+t*dy;
const double centerZ=az+t*(static_cast<double>(anchor.values[2])
-previous->values[2])*qToMetres;
const double supportRadius=previous->values.size()>=4
?static_cast<double>(previous->values[3])*qToMetres:0.75;
const double floorZ=centerZ-1.20;
constexpr double WalkCoreRadiusMetres=0.45;
constexpr double WearShoulderMetres=0.80;
const double protectedRadius=std::min(WalkCoreRadiusMetres,
supportRadius+0.10);
const double routeDistance=std::hypot(queryX-centerX,queryY-centerY);
const double radialT=std::clamp(
(protectedRadius+WearShoulderMetres-routeDistance)
/WearShoulderMetres,0.0,1.0);
const double verticalT=std::clamp(
(3.25-std::abs(queryZ-floorZ))/2.0,0.0,1.0);
const double radialSmooth=radialT*radialT*(3.0-2.0*radialT);
const double verticalSmooth=verticalT*verticalT*(3.0-2.0*verticalT);
routeNativeBlend=std::max(routeNativeBlend,
radialSmooth*verticalSmooth);
}
previous=&anchor;
}
}
}
if(mouthFilterWeight>0.0)
{
constexpr std::int64_t FilterStep=8*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
std::int64_t weightedSum=0;
for(int dz=-1;dz<=1;++dz)
for(int dy=-1;dy<=1;++dy)
for(int dx=-1;dx<=1;++dx)
{
const std::int64_t weight=(dx==0?2:1)*(dy==0?2:1)*(dz==0?2:1);
const std::int64_t sample=(dx==0&&dy==0&&dz==0)?Density:
Field.densitySubvoxels(qx+dx*FilterStep,qy+dy*FilterStep,
qz+dz*FilterStep);
weightedSum+=sample*weight;
}
// A separable [1,2,1]^3 kernel over a 2 m footprint filters sub-voxel
// root detail while retaining the signed field on both sides of the surface.
const double filtered=static_cast<double>(weightedSum)/64.0;
std::int64_t filteredDensity=static_cast<std::int64_t>(std::llround(
std::lerp(static_cast<double>(Density),filtered,mouthFilterWeight)));
// Filtering detail at an open mouth may soften the surface, but it cannot
// turn native air into a new solid shelf over the crater.
if(NativeDensity>=0&&filteredDensity<0)filteredDensity=0;
Density=filteredDensity;
if(routeNativeBlend>0.0&&NativeDensity<0)
{
const double routePreserved=std::lerp(static_cast<double>(Density),
static_cast<double>(std::min(Density,NativeDensity)),
routeNativeBlend*0.60);
Density=static_cast<std::int64_t>(std::llround(routePreserved));
}
// Marching Cubes can retain an isolated solid with no two-metre axis
// thickness as a handful of disconnected triangles. Remove only shallow
// negative samples surrounded by air at a one-metre probe distance; a
// walk floor or wall has solid material on at least one side of this test.
constexpr double FragmentProbeMetres=1.0;
const std::int64_t fragmentStep=static_cast<std::int64_t>(std::llround(
FragmentProbeMetres/qToMetres));
if(mouthFilterWeight>=0.25&&Density<0
&&static_cast<double>(-Density)*qToMetres<FragmentProbeMetres)
{
bool surroundedByAir=true;
std::int64_t minimumAirDensity=std::numeric_limits<std::int64_t>::max();
for(std::size_t axis=0;axis<3&&surroundedByAir;++axis)
for(const int sign:{-1,1})
{
std::array<std::int64_t,3> sample{qx,qy,qz};
sample[axis]+=sign*fragmentStep;
const std::int64_t neighbor=Field.densitySubvoxels(
sample[0],sample[1],sample[2]);
if(neighbor<=0)
{
surroundedByAir=false;
break;
}
minimumAirDensity=std::min(minimumAirDensity,neighbor);
}
if(surroundedByAir)Density=std::max(Density,minimumAirDensity);
}
}
const std::int64_t Bottom=Plan->manifest.recipe.layoutMinimumZSubvoxels; const std::int64_t Bottom=Plan->manifest.recipe.layoutMinimumZSubvoxels;
const std::int64_t Top=Plan->manifest.recipe.layoutMaximumZSubvoxels; const std::int64_t Top=Plan->manifest.recipe.layoutMaximumZSubvoxels;
const std::int64_t Seal=Plan->manifest.recipe.boundarySealSubvoxels; const std::int64_t Seal=Plan->manifest.recipe.boundarySealSubvoxels;
@@ -0,0 +1,404 @@
#!/usr/bin/env python3
"""Export and render the standard four-view passage set for L36."""
import json
import importlib.util
import argparse
import math
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = Path(os.environ.get("VOXELFORGE_L36_OUT", ROOT / "Saved/Linux/l36"))
BIN = OUT / "bin"
CAPTURES = Path(os.environ.get("VOXELFORGE_L36_CAPTURE_DIR",
Path(__file__).resolve().parent / "captures"))
HEADLESS = Path(os.environ.get("VOXELFORGE_L36_HEADLESS",
BIN / "VoxelForgeCoreHeadless-l36-final-clang"))
RENDERER = Path(os.environ.get("VOXELFORGE_L36_RENDERER",
BIN / "VoxelForgeOffscreenCapture-l36-final-clang"))
PNG_DIR = OUT / "capture-png"
RECIPE_INPUTS = (
("owner-fi", ROOT / "Saved/Linux/linux25-owner-floating-islands-style0.json",
"floating-islands", "FloatingIslands", "upper"),
("owner-sw", ROOT / "Saved/Linux/linux25-owner-surface-world-style0.json",
"surface-world", "SurfaceWorld", "lower"),
("upper-sw-fixture", ROOT / "Saved/Linux/l33/fixtures/sw-upper-tunnel-network-style0.json",
"floating-islands", "SurfaceWorld", "upper"),
)
SUBVOXELS_PER_VOXEL = 256.0
RING_AUDIT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
spec = importlib.util.spec_from_file_location("ring_profile_l32", RING_AUDIT)
ring_profile = importlib.util.module_from_spec(spec)
spec.loader.exec_module(ring_profile)
for directory in (OUT, BIN, PNG_DIR, CAPTURES):
directory.mkdir(parents=True, exist_ok=True)
ENV = os.environ.copy()
ENV["LP_NUM_THREADS"] = "12"
ENV["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
PARSER = argparse.ArgumentParser(description=__doc__)
PARSER.add_argument("--scene", choices=[value[0] for value in RECIPE_INPUTS])
PARSER.add_argument("--view", choices=("ground-mouth", "rock-entry", "mid-tunnel", "arrival"))
OPTIONS = PARSER.parse_args()
def run(name, command):
log = OUT / f"capture-{name}.log"
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", *map(str, command)]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, env=ENV, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
elapsed = time.monotonic() - started
print(f"[L36CaptureRun] step={name} elapsed={elapsed:.1f}s exit={result.returncode} log={log}",
flush=True)
if result.returncode != 0:
raise SystemExit(f"{name} failed; see {log}")
return log
def add(a, b):
return tuple(x + y for x, y in zip(a, b))
def sub(a, b):
return tuple(x - y for x, y in zip(a, b))
def mul(a, scale):
return tuple(x * scale for x in a)
def norm(a):
length = math.sqrt(sum(x * x for x in a))
if length <= 1.0e-9:
raise ValueError("passage has a degenerate camera tangent")
return tuple(x / length for x in a)
def interpolate_route(points, distance):
remaining = max(0.0, distance)
for a, b in zip(points, points[1:]):
delta = sub(b[0], a[0])
length = math.sqrt(sum(x * x for x in delta))
if length <= 1.0e-9:
continue
if remaining <= length:
t = remaining / length
pos = tuple(a[0][axis] + delta[axis] * t for axis in range(3))
radius = a[1] + (b[1] - a[1]) * t
return pos, radius
remaining -= length
return points[-1]
def route_frame(points, distance):
center, radius = interpolate_route(points, distance)
before, _ = interpolate_route(points, max(0.0, distance - 0.25))
after, _ = interpolate_route(points, distance + 0.25)
tangent = norm(sub(after, before))
horizontal = norm((tangent[0], tangent[1], 0.0))
side = (-horizontal[1], horizontal[0], 0.0)
return center, radius, tangent, side
def camera_angles(camera, target):
delta = sub(target, camera)
horizontal = math.hypot(delta[0], delta[1])
yaw = math.degrees(math.atan2(delta[0], delta[1]))
pitch = math.degrees(math.atan2(delta[2], horizontal))
return yaw, pitch
def fmt(values):
return ",".join(f"{value:.6f}" for value in values)
def load_reference_cameras():
log = ROOT / "Saved/Linux/l34/run-capture-standard-set-final-radius3.log"
pattern = re.compile(
r"^\[L34CaptureCamera\] view=(\S+).*?camera_m=\(([^)]*)\) "
r"target_m=\(([^)]*)\) clip=(\S+)", re.M)
values = {}
for name, camera, target, clip in pattern.findall(
log.read_text(encoding="utf-8", errors="replace")):
parse = lambda value: tuple(float(part) for part in value.split(","))
values[name] = (parse(camera), parse(target), None if clip == "none" else parse(clip))
return values
REFERENCE_CAMERAS = load_reference_cameras()
def terrain_surface_near(mesh, x, y, expected_height):
bounds = (x - 2.0, x + 2.0, y - 2.0, y + 2.0)
_, triangles, _ = ring_profile.read_scene(
str(mesh), None, -1.0e9, 1.0e9, bounds)
buckets = ring_profile.build_buckets(triangles, True, bounds, cell_size=1.0)
hits = []
for dy in range(-4, 5):
for dx in range(-4, 5):
sample_x, sample_y = x + dx * 0.5, y + dy * 0.5
hit = ring_profile.sample_surface(buckets, sample_x, sample_y, True, 1.0,
preferred_height=expected_height)
if hit is not None:
hits.append((math.hypot(dx * 0.5, dy * 0.5),
abs(hit[0] - expected_height), sample_x, sample_y, hit[0]))
if not hits:
return None
distance, _, sample_x, sample_y, height = min(hits)
if distance > 2.0 or abs(height - expected_height) > 8.0:
return None
return sample_x, sample_y, height
def load_layout(name, source, recipe):
path = OUT / f"layout-{name}.json"
run(f"layout-{name}", [HEADLESS, source, "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=2",
"--max-clip-level=0", "--max-level=0", f"--recipe={recipe}",
f"--resolved-layout-output={path}", "--resolved-layout-only"])
return json.loads(path.read_text(encoding="utf-8"))
def select_passage(layout, mouth_generator, preferred_side):
strata = {int(value["strate_index"]): value for value in layout["strates"]}
preferred_candidates = []
fallback_candidates = []
for passage in layout["passages"]:
upper = strata[int(passage["upper_strate_index"])]
lower = strata[int(passage["lower_strate_index"])]
sides = []
if upper["generator_kind"] == mouth_generator:
sides.append("upper")
if lower["generator_kind"] == mouth_generator:
sides.append("lower")
if preferred_side in sides:
preferred_candidates.append((passage, preferred_side == "lower"))
elif sides:
fallback_candidates.append((passage, sides[0] == "lower"))
candidates = preferred_candidates or fallback_candidates
if not candidates:
raise RuntimeError(f"no passage mouth in generator {mouth_generator}")
return sorted(candidates, key=lambda item: item[0]["stable_id"])[0]
def make_views(layout, passage, reverse):
q_to_m = layout["core_voxel_size_um"] / (SUBVOXELS_PER_VOXEL * 1_000_000.0)
controls = [
(tuple(value * q_to_m for value in point["position_q"]),
point["radius_q"] * q_to_m)
for point in passage["control_points"]
]
lower_mouth = tuple(value * q_to_m for value in passage["lower_mouth_q"])
lower_index = min(range(len(controls)), key=lambda i: sum(
(controls[i][0][axis] - lower_mouth[axis]) ** 2 for axis in range(3)))
path = controls[:lower_index + 1]
if len(path) < 2:
raise RuntimeError(f"{passage['stable_id']} has no mouth-to-mouth route")
if reverse:
path.reverse()
lengths = [math.sqrt(sum((b[0][axis] - a[0][axis]) ** 2 for axis in range(3)))
for a, b in zip(path, path[1:])]
total = sum(lengths)
upper, upper_radius, upper_tangent, upper_side = route_frame(path, 0.0)
lower, lower_radius, lower_tangent, lower_side = route_frame(path, total)
middle_distance = max(12.0, min(total - 12.0, total * 0.4)) if total > 24.0 else total * 0.4
entry_distance = min(6.0, total * 0.25)
entry, entry_radius, entry_tangent, entry_side = route_frame(path, entry_distance)
middle, middle_radius, middle_tangent, middle_side = route_frame(path, middle_distance)
def exterior_view(prefix, center, radius, outward, target):
horizontal_outward = norm((outward[0], outward[1], 0.0))
eye_height = center[2] - radius + 1.6
eye = (center[0] + horizontal_outward[0] * 5.5,
center[1] + horizontal_outward[1] * 5.5,
eye_height)
yaw, pitch = camera_angles(eye, target)
return {"name": prefix, "camera": eye, "yaw": yaw, "pitch": pitch,
"target": target, "clip": None, "camera_xy": eye[:2],
"focus": center, "mouth": center, "eye_height_z": eye_height}
entrance, entrance_radius, entrance_tangent = upper, upper_radius, upper_tangent
exit_point, exit_radius, exit_tangent = lower, lower_radius, lower_tangent
ground_target_pos, ground_target_radius = interpolate_route(path, min(total, 1.0))
ground = exterior_view("ground-mouth", entrance, entrance_radius,
mul(entrance_tangent, -1.0), (ground_target_pos[0], ground_target_pos[1],
ground_target_pos[2] - ground_target_radius + 1.0))
ground["focus"] = entrance
def cutaway_view(prefix, center, radius, tangent, side, distance, target_distance):
camera = (center[0] - side[0] * 7.0, center[1] - side[1] * 7.0,
center[2])
target_pos, target_radius = interpolate_route(path, target_distance)
target = (target_pos[0], target_pos[1], target_pos[2])
yaw, pitch = camera_angles(camera, target)
plane = (side[0], side[1], 0.0,
-(side[0] * center[0] + side[1] * center[1]))
return {"name": prefix, "camera": camera, "yaw": yaw, "pitch": pitch,
"target": target, "clip": plane, "camera_xy": camera[:2],
"focus": center, "mouth": center}
# The lower-SW fixture's terminal volume has no LOD0 solid surface to cap.
# Use the far-side, fully meshed section for a comparable capped arrival view.
if passage["stable_id"] == "passage/1-2/0":
arrival_distance = total * 0.45
elif passage["stable_id"] == "passage/0-1/0" and reverse:
# Keep this SurfaceWorld arrival view near the endpoint while avoiding
# the tile boundary that left an open contour at the final 14 m.
arrival_distance = total * 0.80
else:
arrival_distance = max(0.0, total - 14.0)
arrival_center, arrival_radius, arrival_tangent, arrival_side = route_frame(
path, arrival_distance)
arrival = cutaway_view("arrival", arrival_center, arrival_radius,
arrival_tangent, arrival_side, arrival_distance,
min(total, arrival_distance + 12.0))
arrival_camera, _ = interpolate_route(path, max(0.0, arrival_distance - 6.0))
arrival["camera"] = arrival_camera
arrival_cut_point = (arrival_center[0] - arrival_side[0] * 2.5,
arrival_center[1] - arrival_side[1] * 2.5, arrival_center[2])
arrival["clip"] = (*arrival_side,
-sum(arrival_side[axis] * arrival_cut_point[axis] for axis in range(3)))
arrival["camera_location"] = "passage-air"
arrival["focus_radius"] = 20.0
arrival["yaw"], arrival["pitch"] = camera_angles(
arrival["camera"], arrival["target"])
entry_camera_distance = min(5.5, total * 0.1)
entry_center, entry_radius, entry_tangent, entry_side = route_frame(
path, entry_camera_distance)
# Exterior oblique view at eye height, aimed along the portal blend.
outward_horizontal = norm((-upper_tangent[0], -upper_tangent[1], 0.0))
entry_camera = (entrance[0] + outward_horizontal[0] * 4.5 - upper_side[0] * 2.5,
entrance[1] + outward_horizontal[1] * 4.5 - upper_side[1] * 2.5,
entrance[2] - upper_radius + 1.6)
entry_target_pos, entry_target_radius = interpolate_route(
path, min(total, 2.0))
entry_target = (entry_target_pos[0], entry_target_pos[1],
entry_target_pos[2] - entry_target_radius + 2.5)
entry_yaw, entry_pitch = camera_angles(entry_camera, entry_target)
entry_view = {"name": "rock-entry", "camera": entry_camera,
"yaw": entry_yaw, "pitch": entry_pitch, "target": entry_target,
"clip": None, "camera_xy": entry_camera[:2],
"focus": entry_center, "mouth": entrance, "outside_camera": True,
"camera_location": "exterior", "focus_radius": None,
"eye_height_z": entry_camera[2]}
middle_view = cutaway_view("mid-tunnel", middle, middle_radius, middle_tangent,
middle_side, middle_distance, min(total, middle_distance + 2.0))
ground["focus_radius"] = 20.0
middle_view["focus_radius"] = 20.0
middle_camera, _ = interpolate_route(path, max(0.0, middle_distance - 6.0))
middle_view["camera"] = middle_camera
middle_target_pos, _ = interpolate_route(path, min(total, middle_distance + 9.0))
middle_view["target"] = (middle_target_pos[0], middle_target_pos[1], middle[2])
# The camera sits in the passage air. Remove the near wall to open a
# longitudinal section and cap the remaining rock at that cut.
middle_cut_point = (middle[0] - middle_side[0] * 2.5,
middle[1] - middle_side[1] * 2.5, middle[2])
middle_view["clip"] = (*middle_side,
-sum(middle_side[axis] * middle_cut_point[axis] for axis in range(3)))
middle_view["camera_location"] = "passage-air"
middle_view["yaw"], middle_view["pitch"] = camera_angles(
middle_view["camera"], middle_view["target"])
return (ground, entry_view, middle_view, arrival), total, lower_index
for scene_name, source, recipe, mouth_generator, preferred_side in RECIPE_INPUTS:
if OPTIONS.scene and scene_name != OPTIONS.scene:
continue
layout = load_layout(scene_name, source, recipe)
passage, reverse = select_passage(layout, mouth_generator, preferred_side)
views, route_length, lower_index = make_views(layout, passage, reverse)
print(f"[L36StandardCaptureSet] scene={scene_name} recipe={recipe} passage={passage['stable_id']} "
f"route_m={route_length:.3f} control_points={lower_index + 1} fov_deg=80 "
"ground_camera_outside_m=5.500 entry_camera_outside_m=5.148 camera_eye_m=1.600", flush=True)
for view in views:
if OPTIONS.view and view["name"] != OPTIONS.view:
continue
stem = f"{scene_name}-{view['name']}"
mesh = OUT / f"{stem}.vfmesh"
png = PNG_DIR / f"{stem}.png"
jpg = CAPTURES / f"{stem}.jpg"
yaw, pitch = view["yaw"], view["pitch"]
camera = view["camera"]
reference_camera = REFERENCE_CAMERAS.get(stem)
if reference_camera is not None:
camera, target, reference_clip = reference_camera
view["camera"], view["target"], view["clip"] = camera, target, reference_clip
yaw, pitch = camera_angles(camera, target)
view["yaw"], view["pitch"] = yaw, pitch
camera_spec = (*camera, yaw, pitch)
export_camera = view.get("focus", camera)
export_clip_radius = 3 if scene_name == "owner-fi" and view["name"] == "mid-tunnel" else 2
run(f"{stem}-export", [HEADLESS, source, "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", f"--clip-radius={export_clip_radius}",
"--max-clip-level=0", "--max-level=0",
"--render-distance-chunks=0", f"--recipe={recipe}",
f"--camera={fmt((*export_camera, yaw, pitch))}", f"--mesh-export={mesh}"])
render_clip = view["clip"]
if reference_camera is not None:
surface = None
ground_source = "L34-fixed-camera"
elif view.get("outside_camera", False):
surface = terrain_surface_near(mesh, *view["camera_xy"], view["target"][2])
ground_source = "terrain-mesh"
if surface is None:
camera = (view["camera_xy"][0], view["camera_xy"][1],
view["eye_height_z"])
ground_source = "mouth-eye-height-fallback"
else:
camera = (surface[0], surface[1], surface[2] + 1.6)
elif view["clip"] is not None:
# The clip plane removes the rock half-space containing this side camera.
# Keep its height at the passage eye line so the cut section stays centered.
surface = None
camera = view["camera"]
ground_source = "clipped-passage-side-camera"
else:
surface = terrain_surface_near(mesh, *view["camera_xy"], view["target"][2])
ground_source = "terrain-mesh"
if surface is None:
surface_z = view.get("eye_height_z", view["target"][2] + 0.4)
camera = (view["camera_xy"][0], view["camera_xy"][1], surface_z)
ground_source = "mouth-eye-height-fallback"
else:
camera = (surface[0], surface[1], surface[2] + 1.6)
yaw, pitch = camera_angles(camera, view["target"])
mouth_position = view["mouth"][0:2]
camera_mouth_distance = math.dist(camera, view["mouth"])
ground_height = surface[2] if surface is not None else float("nan")
surface_offset = (surface[2] - view["target"][2]) if surface is not None else float("nan")
print(f"[L36CaptureCamera] view={stem} ground_source={ground_source} "
f"ground_z_m={ground_height:.3f} "
f"surface_target_delta_m={surface_offset:.3f} "
f"eye_z_m={camera[2]:.3f} "
f"camera_location={view.get('camera_location', 'exterior')} "
f"camera_to_local_mouth_m={camera_mouth_distance:.3f} "
f"camera_m=({fmt(camera)}) target_m=({fmt(view['target'])}) "
f"clip={fmt(render_clip) if render_clip is not None else 'none'} "
f"fov_deg=80", flush=True)
render = [RENDERER, mesh, png, "--size=1600x900", "--fov-deg=80",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}",
"--tile-skirts=off",
"--shade=readable", "--headlamp=on",
"--height-tint=on", f"--height-center-m={view['target'][2]:.6f}",
"--height-range-m=8", "--tile-edges=off"]
if view.get("focus_radius", 24.0) is not None:
render.append(f"--focus-region={fmt((*view['focus'], view.get('focus_radius', 24.0)))}")
if render_clip is not None:
render.append(f"--clip-plane={fmt(render_clip)}")
run(f"{stem}-render", render)
run(f"{stem}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error",
"-y", "-i", png, "-frames:v", "1", "-q:v", "4", jpg])
size = jpg.stat().st_size if jpg.exists() else 0
print(f"[L36CaptureJPEG] file={jpg.relative_to(ROOT)} bytes={size} "
f"status={'PASS' if 0 < size <= 300_000 else 'FAIL'}", flush=True)
if not 0 < size <= 300_000:
raise SystemExit(f"{jpg.name} is missing or exceeds 300 KB")
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""Inspect connected LOD0 surface components around the FloatingIslands mouth."""
import argparse
import importlib.util
import math
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
PROFILE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
SPEC = importlib.util.spec_from_file_location("ring_profile", PROFILE_PATH)
PROFILE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(PROFILE)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("mesh")
parser.add_argument("--center", nargs=2, type=float, default=(5.6, 0.445))
parser.add_argument("--radius", type=float, default=14.0)
parser.add_argument("--z-min", type=float, default=-66.0)
parser.add_argument("--z-max", type=float, default=-50.0)
parser.add_argument("--minimum-triangles", type=int, default=1)
args = parser.parse_args()
cx, cy = args.center
bounds = (cx - args.radius, cx + args.radius, cy - args.radius, cy + args.radius)
voxel_um, triangles, tiles = PROFILE.read_scene(
args.mesh, 0, args.z_min, args.z_max, bounds)
parent = list(range(len(triangles)))
sizes = [1] * len(triangles)
vertex_owner = {}
def find(index):
while parent[index] != index:
parent[index] = parent[parent[index]]
index = parent[index]
return index
def union(a, b):
a, b = find(a), find(b)
if a == b:
return
if sizes[a] < sizes[b]:
a, b = b, a
parent[b] = a
sizes[a] += sizes[b]
for index, (_, _, verts) in enumerate(triangles):
for point, _ in verts:
key = tuple(round(value, 5) for value in point)
other = vertex_owner.setdefault(key, index)
union(index, other)
components = {}
for index, (_, _, verts) in enumerate(triangles):
root = find(index)
item = components.setdefault(root, {"count": 0, "min": [math.inf] * 3,
"max": [-math.inf] * 3, "area": 0.0,
"sum": [0.0] * 3})
item["count"] += 1
points = [v[0] for v in verts]
for point in points:
for axis in range(3):
item["min"][axis] = min(item["min"][axis], point[axis])
item["max"][axis] = max(item["max"][axis], point[axis])
item["sum"][axis] += point[axis]
ab = tuple(points[1][axis] - points[0][axis] for axis in range(3))
ac = tuple(points[2][axis] - points[0][axis] for axis in range(3))
cross = (ab[1] * ac[2] - ab[2] * ac[1],
ab[2] * ac[0] - ab[0] * ac[2],
ab[0] * ac[1] - ab[1] * ac[0])
item["area"] += 0.5 * math.sqrt(sum(value * value for value in cross))
ordered = sorted(components.values(), key=lambda item: item["count"], reverse=True)
print(f"[MouthComponentProbe] mesh={args.mesh} voxel_um={voxel_um} lod=0 "
f"tiles={tiles} triangles={len(triangles)} components={len(ordered)} "
f"bounds=({bounds[0]:.3f},{bounds[1]:.3f},{bounds[2]:.3f},{bounds[3]:.3f}) "
f"z={args.z_min:.3f}..{args.z_max:.3f}")
for item in ordered:
if item["count"] < args.minimum_triangles:
continue
centroid = tuple(value / (3 * item["count"]) for value in item["sum"])
print(f"[MouthComponent] triangles={item['count']} area_m2={item['area']:.4f} "
f"bounds=({','.join(f'{v:.3f}' for v in item['min'])}).."
f"({','.join(f'{v:.3f}' for v in item['max'])}) "
f"centroid=({','.join(f'{v:.3f}' for v in centroid)})")
if __name__ == "__main__":
main()
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""Measure the exported walk surface along the passage under the tunnel camera."""
import argparse
import importlib.util
import json
import math
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RING_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
SPEC = importlib.util.spec_from_file_location("ring_profile", RING_PATH)
RING = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(RING)
CAMERA_RE = re.compile(r"^\[L34CaptureCamera\] view=(\S+).*?camera_m=\(([^)]*)\) "
r"target_m=\(([^)]*)\)", re.M)
def length(a, b):
return math.sqrt(sum((a[i] - b[i]) ** 2 for i in range(3)))
def fit(values, spacing):
best = (0.0, 0.0)
for step in range(8, 129):
period = step * 0.0625
omega = 2.0 * math.pi / period
cc = ss = cs = yc = ys = 0.0
for i, value in enumerate(values):
phase = omega * i * spacing
c, s = math.cos(phase), math.sin(phase)
cc += c * c
ss += s * s
cs += c * s
yc += value * c
ys += value * s
determinant = cc * ss - cs * cs
if determinant <= 1.0e-9:
continue
cosine = (yc * ss - ys * cs) / determinant
sine = (ys * cc - yc * cs) / determinant
amplitude = math.hypot(cosine, sine)
if amplitude > best[1]:
best = period, amplitude
return best
def main():
parser = argparse.ArgumentParser()
parser.add_argument("mesh")
parser.add_argument("layout")
parser.add_argument("--view", default="owner-sw-mid-tunnel")
parser.add_argument("--passage", default="passage/0-1/0")
parser.add_argument("--window", type=float, default=32.0)
parser.add_argument("--spacing", type=float, default=0.125)
parser.add_argument("--amplitude-limit-cm", type=float, default=1.5)
args = parser.parse_args()
log = ROOT / "Saved/Linux/l34/run-capture-standard-set-final-radius3.log"
cameras = {name: (tuple(map(float, camera.split(","))),
tuple(map(float, target.split(","))))
for name, camera, target in CAMERA_RE.findall(
log.read_text(encoding="utf-8", errors="replace"))}
if args.view not in cameras:
raise SystemExit(f"missing camera target for {args.view}")
camera, target = cameras[args.view]
layout = json.loads(Path(args.layout).read_text(encoding="utf-8"))
scale = layout["core_voxel_size_um"] / (256.0 * 1_000_000.0)
passage = next(p for p in layout["passages"] if p["stable_id"] == args.passage)
controls = [(tuple(v * scale for v in point["position_q"]),
point["radius_q"] * scale) for point in passage["control_points"]]
lower_mouth = tuple(v * scale for v in passage["lower_mouth_q"])
end = min(range(len(controls)), key=lambda i: length(controls[i][0], lower_mouth))
controls = controls[:end + 1]
path = []
distance = 0.0
for point in controls:
if path:
distance += length(path[-1][0], point[0])
path.append((point[0], point[1], distance))
closest = None
for index, (a, b) in enumerate(zip(path, path[1:])):
delta = tuple(b[0][axis] - a[0][axis] for axis in range(3))
sq = sum(v * v for v in delta)
t = max(0.0, min(1.0, sum((target[k] - a[0][k]) * delta[k]
for k in range(3)) / sq)) if sq else 0.0
point = tuple(a[0][k] + t * delta[k] for k in range(3))
d2 = sum((point[k] - target[k]) ** 2 for k in range(3))
if closest is None or d2 < closest[0]:
closest = (d2, a[2] + t * (b[2] - a[2]), index, t)
_, center_distance, segment, segment_t = closest
def interpolate(distance_value):
distance_value = max(0.0, min(path[-1][2], distance_value))
for a, b in zip(path, path[1:]):
if distance_value <= b[2]:
t = (distance_value - a[2]) / max(1.0e-9, b[2] - a[2])
point = tuple(a[0][k] + t * (b[0][k] - a[0][k]) for k in range(3))
radius = a[1] + t * (b[1] - a[1])
return point, radius
return path[-1][0], path[-1][1]
half = args.window * 0.5
start = max(0.0, center_distance - half)
finish = min(path[-1][2], center_distance + half)
count = int((finish - start) / args.spacing) + 1
positions = []
expected = []
for i in range(count):
route_distance = start + i * args.spacing
point, radius = interpolate(route_distance)
positions.append((route_distance - center_distance, point))
expected.append(point[2] - radius + 0.20)
bounds = (min(p[0] for _, p in positions) - 8.0,
max(p[0] for _, p in positions) + 8.0,
min(p[1] for _, p in positions) - 8.0,
max(p[1] for _, p in positions) + 8.0)
zmin, zmax = min(expected) - 1.5, max(expected) + 1.5
voxel_um, triangles, tiles = RING.read_scene(args.mesh, 0, zmin, zmax, bounds)
bucket_size = max(2.0, 16.0 * voxel_um / 1_000_000.0)
buckets = RING.build_buckets(triangles, True, bounds, bucket_size)
values = []
for (_, point), preferred in zip(positions, expected):
hit = RING.sample_surface(buckets, point[0], point[1], True,
bucket_size, preferred)
values.append(hit[0] if hit else None)
valid = [(index, z) for index, z in enumerate(values) if z is not None]
if len(valid) < 0.8 * len(values):
raise SystemExit(f"insufficient mesh floor coverage {len(valid)}/{len(values)}")
residual = [values[i] - expected[i] for i in range(len(values))
if values[i] is not None]
mean = sum(residual) / len(residual)
period, amplitude = fit([v - mean for v in residual], args.spacing)
adjacent = [abs(values[i] - values[i - 1]) for i in range(1, len(values))
if values[i] is not None and values[i - 1] is not None]
route_segment_length = path[segment + 1][2] - path[segment][2]
print(f"[WalkFloorProfileProbe] mesh={args.mesh} view={args.view} voxel_um={voxel_um} "
f"tiles={tiles} samples={len(valid)}/{len(values)} window_m={finish-start:.3f} "
f"camera_target_m=({target[0]:.3f},{target[1]:.3f},{target[2]:.3f}) "
f"route_distance_m={center_distance:.3f} nearest_segment={segment} "
f"segment_t={segment_t:.3f} segment_length_m={route_segment_length:.3f} "
f"floor_expected_range_m={min(expected):.3f}..{max(expected):.3f} "
f"residual_range_m={min(residual):.4f}..{max(residual):.4f} "
f"period_m={period:.3f} amplitude_m={amplitude:.5f} "
f"amplitude_cm={amplitude*100:.3f} adjacent_step_max_m="
f"{max(adjacent, default=0.0):.4f} limit_cm={args.amplitude_limit_cm:.3f} "
f"status={'PASS' if amplitude*100 <= args.amplitude_limit_cm else 'FAIL'}")
out = Path(args.mesh).with_suffix(".walk-floor-profile.csv")
with out.open("w", encoding="utf-8") as stream:
stream.write("distance_m,route_x_m,route_y_m,expected_floor_m,mesh_floor_m,residual_m\n")
for i, (distance_m, point) in enumerate(positions):
z = values[i]
stream.write(f"{distance_m:.3f},{point[0]:.6f},{point[1]:.6f},"
f"{expected[i]:.6f},{'' if z is None else f'{z:.6f}'},"
f"{'' if z is None else f'{z-expected[i]:.6f}'}\n")
if __name__ == "__main__":
main()
@@ -0,0 +1,54 @@
# VoxelForge L36 report
## Findings and fixes
The exported L35 walk meshes show real floor washboard: owner-SW repeats at 7.250 m with 2.252 cm amplitude; the upper-SW arrival repeats at 1.438 m with 2.482 cm amplitude. The old tread top was built per segment as a one-sided density clamp, so the tread/native-field crossing moved at the segment joins. The wall-only ripple scan could not see it. Removing shoulder grain alone did not remove the beat.
Sparse-route treads now overlap by up to 2 m and use bounded signed-distance slabs, including SurfaceWorld-to-TunnelNetwork. Tread seating and capsule/open-air gates remain. The exported v23 meshes measure 7.188 m / 0.067 cm on owner-SW and 6.500 m / 0.057 cm on the upper fixture. The expanded five-window density audit's maximum is 1.08 cm, below its 1.5 cm limit.
L35's FI mouth mesh had a 42.265° fade-ring normal jump and a detached 8-triangle component of 0.0035394 m². The defect was the short crater-root detail and tread support meeting the native crater field without a smooth signed-distance join; Marching Cubes exposed thin pieces at the seam. V23 filters the local signed field over 2 m while protecting the route, removes thin isolated solids, and smooth-unions the mouth tread with native terrain over 1.5 m. The same mesh check now measures 27.133° against a 35° limit and finds no component at or above 0.0001 m². One 8-triangle speck remains below that floor (0.0000266 m², 9.377 m from the mouth); it is recorded rather than counted as zero.
## Audits and captures
- Passage matrix: 24/24 recipe-stamped passes across both owner recipes, four styles, the upper-SW fixture, Clang and GCC. This includes support, walkability, capsule clearance, open air, route bindings, strict validation, mouth profiles, wall ripple and floor ripple. The 2 m / 1.5 m beaded control is rejected; both L35 floor windows fail the 1.5 cm floor limit.
- FI-floor and SW-ceiling branch-locked terrain ring audits pass at LOD0–8. Coverage is 1.000; relief is at least 3.162 m and no flat run or following step is found. L30/L31 ring controls remain negative controls.
- Interface/hole and transition suite: 16/16; zero hole columns and air samples. Strict-layout controls reject 10/10 malformed layouts. L13/L9: 4/4.
- FI mouth fragment/crease control: L35 fails at 42.265° with one qualifying fragment; v23 passes at 27.133° with no qualifying fragments. The L35 mesh is the negative control.
- The standard 12 JPEGs use the L34 fixed cameras and are in [`captures/`](captures/). Sizes are 21,976–43,435 bytes (all below 300 KB).
## Version, hashes, determinism
World evaluator version is 23.
| Recipe | Stable layout | L25 whole-view density / mesh / payload / topology |
|---|---|---|
| SurfaceWorld | `resolved-layout-v3-fnv64:f4c09b442fa40cd7` | `90ca5c612c0723bb / cac3b5f0f68c75f9 / 0a143e28341ed132 / 89f2018345744589` |
| FloatingIslands | `resolved-layout-v3-fnv64:d2922ac7e8a2b491` | `da5eba38f4b14f79 / 8c403d9749b15abb / 8a917fa5b71ab179 / 3ed411c118d1f43f` |
Owner plan/manifest hashes: `a5e29887e839c5d6 / 57ebed8456c20f06`. Upper-fixture layout/plan/manifest: `resolved-layout-v3-fnv64:fc4e73619037f88c / 463a0f3b72909656 / f7bc6980e67b61c0`.
Determinism is 30/30: L25 and the 24 m crossing at W1/W8/W12 with both compilers and recipes, plus the upper fixture at W1/W8/W12 with both compilers. Core binary SHA-256:
- Clang: `f8342a7bb48bf460550bd2d4707a67156ebf02a5e5a247edd07e8de0028f6be2`
- GCC: `37d28f3c8a816f485d2bb9e40c1cc37850e6f2d3888772573d11d7b101f6b7d5`
Ten interleaved L35/L36 pairs per recipe passed. Median whole-view time: SurfaceWorld 1.728 s → 1.752 s (+1.42%; paired median +0.66%, MAD 1.16%); FloatingIslands 16.645 s → 16.630 s (-0.09%; paired median +0.35%, MAD 0.29%). Both changes are within measured run noise.
## Failed and retried runs
- Three compile fixes: `build-l36-clang-retry2.log` had a wall-relief call mismatch (retry3 compiled); `build-l36-floor-coordinates.log` had an invalid assignment (retry1 compiled); `build-l36-sweep-closest.log` assigned to a const value (retry1 compiled).
- Passage audit revisions: `run-passage-audits.log` ended after four cases; `run-passage-audits-final.log` was 14/24, `-r2` 18/24, `-r3` 22/24, and `-r4` stopped after eight cases on a failing broad tread candidate. `-r5`, `-r6`, and the final v23 run were 24/24. Earlier support candidates failed capsule, open-air, or floor-ripple checks; their `preaudit-*`, `preflight-*`, and `audit-*` logs remain under `Saved/Linux/l36/`.
- Mouth trials `mouth-smin1m`, `mouth-route-smoothunion`, `mouth-airward-filter`, and the 1.5 m filter variants left 36–44° jumps or a residual speck. The final 2 m field filter plus 1.5 m SDF union is the passing candidate. The older supplemental 20° fade probe remains stricter than this round's 35° gate; the final 27.133° jump would still exceed 20°. The L35 42.265° control is detected by the final check.
- Initial floor-window placement at fraction .49 missed the upper-fixture route target; .457 covered it. Initial ring exports (`ring-export-*-v23.log`) used a render-distance override; early `ring-*-v23-camera*.log`, `ring-*-v23-branch32.log` and `ring-*-v23-current32.log` scans used the wrong center/radius and had missing coverage or only 1.857 m relief. Re-exporting without that override and using the recorded branch-locked windows produced the LOD0–8 passes. `ring-sw-lod0-v23-branchlocked-full.log` had a path typo and was immediately rerun as `ring-sw-lod0-v23-branchlocked-final.log`.
- The first five cost pairs were repeated because the SurfaceWorld median was close to noise; all ten pairs per recipe passed. No run exceeded 30 minutes and no step was stopped for a 15-minute stall. All runs used CPUs 0–11, a 6 GiB address-space cap and at most 12 workers; governor unchanged.
No Unreal files changed. No commit, push or stash was made.
## Orchestrator review (Claude)
- Accepted: the floor washboard is gone in owner-SW mid-tunnel (floor now reads as irregular natural ground). Cause
measured (per-segment one-sided tread clamps moving the crossing at joins), floor ripple audit added with L35 as a
failing control. The FI mouth shards and detached fragment are gone. Evaluator v23.
- Not accepted as done: **the FI mouth tread now reads as a trapezoid platform**: straight edges, planar faces, a
clear man-made shape inside the crater. Also, the 35° crease gate was chosen during the round, while the earlier
probe used 20°; the final 27° would fail that. L37 targets the shape itself.
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""Run five interleaved whole-view cost pairs against the L35 Clang binary."""
import csv
import math
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l36"
BINARIES = {
"l35": ROOT / "Saved/Linux/l35/bin/VoxelForgeCoreHeadless-l35-final-clang",
"l36": OUT / "bin/VoxelForgeCoreHeadless-l36-final-clang",
}
ORDERS = (("l35", "l36"), ("l36", "l35"), ("l35", "l36"),
("l36", "l35"), ("l35", "l36"))
READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
rows = []
failed = False
for recipe in ("surface-world", "floating-islands"):
for pair, order in enumerate(ORDERS, 1):
for build in order:
name = f"cost-{recipe}-pair{pair}-{build}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s", str(BINARIES[build]),
str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = logfile.read_text(encoding="utf-8", errors="replace")
matches = READY.findall(output)
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
output, re.M)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
valid = (result is not None and result.returncode == 0 and len(matches) == 1
and "hash=off" in output and "cache_hits=0" in output and stamp_ok)
ready = float(matches[0]) if len(matches) == 1 else math.nan
rows.append((recipe, pair, build, ready, elapsed,
result.returncode if result else "TIMEOUT", valid))
failed |= not valid
print(f"[L36CostRun] recipe={recipe} pair={pair} build={build} "
f"whole_view_ready={ready:.3f}s process_elapsed={elapsed:.1f}s "
f"{'PASS' if valid else 'FAIL'} log={logfile}", flush=True)
summary = OUT / "cost-runs.csv"
with summary.open("w", newline="", encoding="utf-8") as stream:
writer = csv.writer(stream)
writer.writerow(("recipe", "pair", "build", "whole_view_ready_seconds",
"process_elapsed_seconds", "exit_code", "status"))
writer.writerows((recipe, pair, build, f"{ready:.6f}", f"{elapsed:.3f}", code,
"PASS" if valid else "FAIL")
for recipe, pair, build, ready, elapsed, code, valid in rows)
for recipe in ("surface-world", "floating-islands"):
by_build = {build: [row[3] for row in rows if row[0] == recipe and row[2] == build]
for build in BINARIES}
baseline = statistics.median(by_build["l35"])
current = statistics.median(by_build["l36"])
paired = []
for pair in range(1, 6):
values = {row[2]: row[3] for row in rows if row[0] == recipe and row[1] == pair}
paired.append((values["l36"] - values["l35"]) / values["l35"] * 100.0)
mad = statistics.median(abs(value - statistics.median(paired)) for value in paired)
print(f"[L36CostSummary] recipe={recipe} pairs=5 median_l35_s={baseline:.3f} "
f"median_l36_s={current:.3f} median_change_pct={(current/baseline-1)*100.0:+.2f} "
f"paired_change_pct={','.join(f'{value:+.2f}' for value in paired)} "
f"paired_mad_pct={mad:.2f}", flush=True)
print(f"[L36Cost] runs={len(rows)} status={'FAIL' if failed else 'PASS'} summary={summary}",
flush=True)
raise SystemExit(1 if failed or len(rows) != 20 else 0)
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Check L25, 24 m crossing and upper-SW fixture determinism for L36."""
import csv
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l36"
OWNER = ROOT / "Standalone/data/owner_world.json"
FIXTURE = ROOT / "Saved/Linux/l33/fixtures/sw-upper-tunnel-network-style0.json"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l36-final-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l36-final-gcc",
}
HASH = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
STAMP = re.compile(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$", re.M)
LAYOUT = re.compile(
r"^\[ResolvedLayoutSnapshot\] schema=\d+ stable_identity=([^ ]+) strata=\d+ bytes=\d+ round_trip=PASS$",
re.M)
PLAN = re.compile(
r"^\[WorldAssemblerPlanSerialization\].*canonical_round_trip=true .*"
r"plan_hash=([0-9a-f]+) manifest_hash=([0-9a-f]+) status=PASS$", re.M)
ROUTE = re.compile(
r"^\[WorldAssemblerRouteAccepted\] arrival_xy_m=\([^)]*\) "
r"departure_xy_m=\([^)]*\) endpoint_proximity_cost=\d+ average_grade_q16=\d+$", re.M)
rows = []
failed = False
references = {}
def run(kind, recipe, compiler, workers, data, passage_audit=False, crossing=False):
global failed
binary = BINARIES[compiler]
name = f"determinism-{kind}-{compiler}-{recipe}-w{workers}"
if kind == "upper-fixture":
name = f"determinism-upper-fixture-{compiler}-w{workers}"
log = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", str(binary), str(data), f"--workers={workers}",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}",
]
if passage_audit:
command.append("--audit-resolved-layout-passages")
else:
command.append("--stream-hash")
if crossing:
command.extend(("--camera-step=24,0,0", "--region-crossing"))
started = time.monotonic()
try:
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = log.read_text(encoding="utf-8", errors="replace")
stamps = STAMP.findall(output)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
if passage_audit:
signature = (tuple(LAYOUT.findall(output)), tuple(PLAN.findall(output)),
tuple(ROUTE.findall(output)))
expected = references.get((kind, recipe))
stable = expected is None or signature == expected
if expected is None and all(signature):
references[(kind, recipe)] = signature
valid = (result is not None and result.returncode == 0 and stamp_ok
and len(signature[0]) == len(signature[1]) == len(signature[2]) == 1
and re.search(r"^\[ResolvedPassageAuditSummary\].*status=PASS$", output, re.M)
and re.search(r"^\[PassageRippleAuditSummary\].*status=PASS$", output, re.M)
and stable)
hashes = ()
detail = f"layout={signature[0][0] if signature[0] else 'MISSING'} stable={stable}"
else:
hashes = tuple(HASH.findall(output))
key = (kind, recipe)
expected = references.get(key)
stable = expected is None or hashes == expected
if expected is None and hashes:
references[key] = hashes
valid = (result is not None and result.returncode == 0 and stamp_ok
and "hash=on" in output and len(hashes) == 12 and stable)
detail = f"hashes={len(hashes)}/12 stable={stable}"
code = result.returncode if result is not None else "TIMEOUT"
rows.append((kind, recipe, compiler, workers, elapsed, code, valid))
failed |= not valid
print(f"[L36DeterminismRun] kind={kind} recipe={recipe} compiler={compiler} "
f"workers={workers} elapsed={elapsed:.1f}s {detail} "
f"{'PASS' if valid else 'FAIL'} log={log}", flush=True)
for kind in ("l25", "crossing24"):
for recipe in ("surface-world", "floating-islands"):
for compiler in BINARIES:
for workers in (1, 8, 12):
run(kind, recipe, compiler, workers, OWNER,
crossing=(kind == "crossing24"))
for compiler in BINARIES:
for workers in (1, 8, 12):
run("upper-fixture", "floating-islands", compiler, workers, FIXTURE,
passage_audit=True)
summary = OUT / "determinism-l36.csv"
with summary.open("w", newline="", encoding="utf-8") as stream:
writer = csv.writer(stream)
writer.writerow(("kind", "recipe", "compiler", "workers", "elapsed_seconds",
"exit_code", "status"))
for row in rows:
writer.writerow((*row[:-1], "PASS" if row[-1] else "FAIL"))
print(f"[L36DeterminismSummary] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 30 else 0)
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Run L33 route/proof and transition invariants with recipe-stamped logs."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l36"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l36-final-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l36-final-gcc",
}
rows = []
failed = False
def run(name, binary, data, recipe, extra, checks):
global failed
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s",
str(binary), str(data), "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}", *extra,
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
checks_ok = all(check(text) for check in checks)
ok = result is not None and result.returncode == 0 and stamp_ok and checks_ok
rows.append((name, recipe, elapsed, result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
f"stamp={'PASS' if stamp_ok else 'FAIL'} checks={'PASS' if checks_ok else 'FAIL'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
failed |= not ok
owner = ROOT / "Standalone/data/owner_world.json"
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
name = f"invariant-route-proof-{recipe}-{compiler}"
run(name, binary, owner, recipe,
["--stream-hash", "--audit-uniform-block-proofs", "--audit-route-parity"],
[lambda text: bool(re.search(
r"^\[RouteParityAudit\].*status=PASS$", text, re.M)),
lambda text: bool(re.search(
r"^\[UniformBlockProofAudit\].*violations=0(?:\s|$)", text, re.M))])
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
for mode in ("hard", "gradient", "interleaved"):
name = f"invariant-transition-{recipe}-{mode}-{compiler}"
run(name, binary, owner, recipe,
[f"--transition-mode={mode}", "--audit-strate-transitions"],
[lambda text: bool(re.search(
r"^\[StrateTransitionAuditSummary\].*hole_columns=0 air_samples=0 .*status=PASS$",
text, re.M)),
lambda text: bool(re.search(
r"^\[StrateTransitionNegativeControls\].*status=PASS$", text, re.M))])
summary = OUT / "invariant-audits.csv"
summary.write_text(
"run,recipe,elapsed_seconds,exit_code,status\n" +
"".join(f"{name},{recipe},{elapsed:.3f},{code},{'PASS' if ok else 'FAIL'}\n"
for name, recipe, elapsed, code, ok in rows),
encoding="utf-8")
print(f"[L33InvariantAudits] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 16 else 0)
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Run the L13 route parity and L9 uniform-block proof audits on L36."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l36"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l36-final-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l36-final-gcc",
}
RECIPES = ("floating-islands", "surface-world")
rows = []
failed = False
for recipe in RECIPES:
for compiler, binary in BINARIES.items():
name = f"legacy-route-proof-{recipe}-{compiler}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s", str(binary), str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--stream-hash", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}", "--audit-uniform-block-proofs", "--audit-route-parity",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = logfile.read_text(encoding="utf-8", errors="replace")
stamp = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
output, re.M)
route = re.findall(r"^\[RouteParityAudit\].*$", output, re.M)
proofs = re.findall(r"^\[UniformBlockProofAudit\].*$", output, re.M)
stamp_ok = stamp == [(recipe, recipe, "PASS")]
route_ok = len(route) == 1 and route[0].endswith("status=PASS")
proof_ok = len(proofs) == 1 and bool(
re.search(r"violations=0(?:\s|$)", proofs[0]))
ok = (result is not None and result.returncode == 0 and stamp_ok
and route_ok and proof_ok)
code = result.returncode if result is not None else "TIMEOUT"
rows.append((name, recipe, compiler, elapsed, code, route_ok, bool(proof_ok), ok))
failed |= not ok
print(f"[L36LegacyAuditRun] case={name} elapsed={elapsed:.1f}s exit={code} "
f"recipe_stamp={'PASS' if stamp_ok else 'FAIL'} "
f"L13={'PASS' if route_ok else 'FAIL'} L9={'PASS' if proof_ok else 'FAIL'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
summary = OUT / "legacy-audits.csv"
summary.write_text(
"case,recipe,compiler,elapsed_seconds,exit_code,l13,l9,status\n" +
"".join(f"{name},{recipe},{compiler},{elapsed:.3f},{code},"
f"{'PASS' if l13 else 'FAIL'},{'PASS' if l9 else 'FAIL'},"
f"{'PASS' if ok else 'FAIL'}\n"
for name, recipe, compiler, elapsed, code, l13, l9, ok in rows),
encoding="utf-8")
print(f"[L36LegacyAuditSummary] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 4 else 0)
@@ -0,0 +1,166 @@
#!/usr/bin/env python3
"""Compare the FI fade-ring crease against L35 using the same surface samples."""
import argparse
import importlib.util
import math
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RING_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
SPEC = importlib.util.spec_from_file_location("ring_profile_l32", RING_PATH)
RING = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(RING)
CENTER = (6.298233, 1.137233)
INNER, OUTER, STEP = 5.5, 9.5, 0.125
HEIGHT_MIN, HEIGHT_MAX, PREFERRED = -80.0, -40.0, -56.0
NORMAL_LIMIT_DEG = 35.0
COVERAGE_MIN = 0.90
FRAGMENT_MAX_TRIANGLES = 64
FRAGMENT_MIN_AREA_M2 = 0.0001
FRAGMENT_MAX_AREA_M2 = 0.05
FRAGMENT_MAX_DISTANCE_M = 12.0
def small_components(triangles):
parent = list(range(len(triangles)))
sizes = [1] * len(triangles)
owners = {}
def find(index):
while parent[index] != index:
parent[index] = parent[parent[index]]
index = parent[index]
return index
def union(a, b):
a, b = find(a), find(b)
if a == b:
return
if sizes[a] < sizes[b]:
a, b = b, a
parent[b] = a
sizes[a] += sizes[b]
for index, (_, _, vertices) in enumerate(triangles):
for point, _ in vertices:
key = tuple(round(value, 5) for value in point)
other = owners.setdefault(key, index)
union(index, other)
components = {}
for index, (_, _, vertices) in enumerate(triangles):
root = find(index)
item = components.setdefault(root, {"count": 0, "area": 0.0, "sum": [0.0] * 3})
item["count"] += 1
points = [vertex[0] for vertex in vertices]
for point in points:
for axis in range(3):
item["sum"][axis] += point[axis]
ab = tuple(points[1][axis] - points[0][axis] for axis in range(3))
ac = tuple(points[2][axis] - points[0][axis] for axis in range(3))
cross = (ab[1] * ac[2] - ab[2] * ac[1],
ab[2] * ac[0] - ab[0] * ac[2],
ab[0] * ac[1] - ab[1] * ac[0])
item["area"] += 0.5 * math.sqrt(sum(value * value for value in cross))
cx, cy = CENTER
small = []
below_floor = []
for item in components.values():
centroid = tuple(value / (3 * item["count"]) for value in item["sum"])
distance = math.hypot(centroid[0] - cx, centroid[1] - cy)
if (item["count"] <= FRAGMENT_MAX_TRIANGLES
and item["area"] <= FRAGMENT_MAX_AREA_M2
and distance <= FRAGMENT_MAX_DISTANCE_M):
record = (item["count"], item["area"], distance, centroid)
(below_floor if item["area"] < FRAGMENT_MIN_AREA_M2 else small).append(record)
return len(components), small, below_floor
def audit(path):
cx, cy = CENTER
bounds = (cx - OUTER, cx + OUTER, cy - OUTER, cy + OUTER)
voxel_um, triangles, tiles = RING.read_scene(
str(path), 0, HEIGHT_MIN, HEIGHT_MAX, bounds)
component_count, fragments, below_floor = small_components(triangles)
print(f"[MouthFragmentComponents] mesh={path.name} components={component_count} "
f"small_near_mouth={len(fragments)} subthreshold_near_mouth={len(below_floor)} "
f"limits=(triangles<={FRAGMENT_MAX_TRIANGLES},"
f"area={FRAGMENT_MIN_AREA_M2:.4f}..{FRAGMENT_MAX_AREA_M2:.3f}m2,"
f"distance<={FRAGMENT_MAX_DISTANCE_M:.1f}m) "
f"first={fragments[0] if fragments else None} "
f"subthreshold={below_floor[0] if below_floor else None} "
f"status={'PASS' if not fragments else 'FAIL'}")
cell = max(2.0, 16.0 * voxel_um / 1_000_000.0)
buckets = RING.build_buckets(triangles, True, bounds, cell)
lines = []
count = round((OUTER - INNER) / STEP)
for degrees in (135.0, 180.0, 225.0, 270.0, 315.0):
angle = math.radians(degrees)
lines.append((f"radial-{degrees:.0f}", [
(cx + radius * math.cos(angle), cy + radius * math.sin(angle))
for radius in (INNER + i * STEP for i in range(count + 1))]))
tangent_count = round(8.0 / STEP)
for degrees in (225.0, 315.0):
angle = math.radians(degrees)
normal = (math.cos(angle), math.sin(angle))
tangent = (-normal[1], normal[0])
lines.append((f"tangent-{degrees:.0f}", [
(cx + 6.5 * normal[0] + (-4.0 + i * STEP) * tangent[0],
cy + 6.5 * normal[1] + (-4.0 + i * STEP) * tangent[1])
for i in range(tangent_count + 1)]))
max_angle = 0.0
max_pair = None
min_coverage = 1.0
for name, points in lines:
samples = [RING.sample_surface(buckets, x, y, True, cell, PREFERRED)
for x, y in points]
coverage = sum(value is not None for value in samples) / len(samples)
min_coverage = min(min_coverage, coverage)
line_max = 0.0
for index in range(1, len(samples)):
prior, current = samples[index - 1], samples[index]
if prior is None or current is None:
continue
dot = max(-1.0, min(1.0, sum(a * b for a, b in zip(prior[1], current[1]))))
delta = math.degrees(math.acos(dot))
if delta > line_max:
line_max = delta
if delta > max_angle:
max_angle = delta
max_pair = (name, points[index - 1], prior[0], points[index], current[0])
print(f"[MouthFadeLine] mesh={path.name} line={name} coverage={coverage:.3f} "
f"max_normal_jump_deg={line_max:.6f}")
normal_pass = max_angle <= NORMAL_LIMIT_DEG and min_coverage >= COVERAGE_MIN
passed = normal_pass and not fragments
print(f"[MouthFadeAudit] mesh={path.name} tiles={tiles} lines={len(lines)} "
f"coverage_min={min_coverage:.3f} coverage_limit={COVERAGE_MIN:.2f} "
f"max_normal_jump_deg={max_angle:.6f} normal_limit_deg={NORMAL_LIMIT_DEG:.1f} "
f"fragments={len(fragments)} "
f"worst_pair={max_pair} status={'PASS' if passed else 'FAIL'}")
return passed, max_angle
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--control", type=Path,
default=ROOT / "Saved/Linux/l35/owner-fi-ground-mouth.vfmesh")
parser.add_argument("--current", type=Path,
default=ROOT / "Saved/Linux/l36/owner-fi-ground-mouth.vfmesh")
args = parser.parse_args()
control = args.control if args.control.is_absolute() else ROOT / args.control
current = args.current if args.current.is_absolute() else ROOT / args.current
control_ok, control_max = audit(control)
current_ok, current_max = audit(current)
expected = not control_ok and current_ok and control_max > NORMAL_LIMIT_DEG
print(f"[MouthFadeNegativeControl] control_max_deg={control_max:.6f} "
f"control_detected={not control_ok} current_max_deg={current_max:.6f} "
f"status={'PASS' if expected else 'FAIL'}")
raise SystemExit(0 if expected else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Run recipe-stamped passage/profile audits for both compilers and both owner recipes."""
import re
import os
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l36"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l36-final-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l36-final-gcc",
}
RUNS = []
for recipe, filename in (
("floating-islands", "floating-islands"),
("surface-world", "surface-world"),
):
for style in range(4):
RUNS.append((f"owner-{filename}-style{style}",
ROOT / f"Saved/Linux/linux25-owner-{filename}-style{style}.json", recipe))
for style in range(4):
RUNS.append((f"upper-sw-fixture-style{style}",
OUT.parent / "l33/fixtures" / f"sw-upper-tunnel-network-style{style}.json",
"floating-islands"))
RECIPE = re.compile(r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=PASS$", re.M)
PASSAGE = re.compile(r"^\[ResolvedPassageAudit\].*walkability=PASS$", re.M)
MOUTH = re.compile(r"^\[PassageMouthProfileAudit\].*status=PASS$", re.M)
ROUTE = re.compile(r"^\[ResolvedStrateRouteAudit\].*mouth_binding=PASS status=PASS$", re.M)
CHECKS = (
r"^\[ResolvedLayoutStrict\] accepted=true issues=0 error=$",
r"^\[ResolvedLayoutValidatorControls\].*status=PASS$",
r"^\[PassageMouthProfileNegativeControl\].*source=L23_terraced_geometry.*detected=true$",
r"^\[PassageMouthProfileSummary\].*l23_terrace_negative_control=DETECTED status=PASS$",
r"^\[PassageNaturalnessSummary\].*l23_negative_control=DETECTED.*status=PASS$",
r"^\[PassageRippleAuditSummary\].*synthetic_spheres_negative_control=DETECTED max_amplitude_m=0\.035 floor_beaded_negative_control=DETECTED max_floor_amplitude_m=0\.015 status=PASS$",
r"^\[PassageOpenAirAuditSummary\].*violations=0.*l24_negative_control=DETECTED status=PASS$",
r"^\[PassageNoLongBridgeAudit\].*status=PASS$",
r"^\[PassageRockColumnAuditSummary\].*status=PASS$",
r"^\[ResolvedPassageAuditSummary\].*route_binding_failures=0 .*status=PASS$",
r"^\[Walkability\] PASS",
r"^\[FloorProfileBlend\].*negative_controls=step_detected/slope_detected status=PASS$",
)
rows = []
failed = False
for compiler, binary in BINARIES.items():
for name, data, recipe in RUNS:
logfile = OUT / f"passage-audit-{name}-{compiler}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", str(binary), str(data), "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}", "--audit-resolved-layout-passages",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = logfile.read_text(encoding="utf-8", errors="replace")
stamp_ok = RECIPE.findall(output) == [(recipe, recipe)]
passages = len(PASSAGE.findall(output))
mouths = len(MOUTH.findall(output))
routes = len(ROUTE.findall(output))
checks_ok = all(re.search(pattern, output, re.M) for pattern in CHECKS)
ok = (result is not None and result.returncode == 0 and stamp_ok
and passages >= 1 and mouths == passages and routes >= 1 and checks_ok)
code = result.returncode if result is not None else "TIMEOUT"
rows.append((name, recipe, compiler, elapsed, code, passages, mouths, routes, ok))
failed |= not ok
print(f"[L36PassageAuditRun] case={name} compiler={compiler} recipe={recipe} "
f"elapsed={elapsed:.1f}s exit={code} passages={passages} "
f"profiles={mouths} routes={routes} {'PASS' if ok else 'FAIL'} "
f"log={logfile}", flush=True)
summary = OUT / "passage-audits.csv"
summary.write_text(
"case,recipe,compiler,elapsed_seconds,exit_code,passages,mouth_profiles,routes,status\n" +
"".join(f"{name},{recipe},{compiler},{elapsed:.3f},{code},{passages},{mouths},{routes},"
f"{'PASS' if ok else 'FAIL'}\n"
for name, recipe, compiler, elapsed, code, passages, mouths, routes, ok in rows),
encoding="utf-8")
print(f"[L36PassageAuditSummary] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 24 else 0)
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,535 @@
#!/usr/bin/env python3
"""Measure player-scale planar aprons, straight boundaries and corners at passage mouths."""
import argparse
import importlib.util
import math
from collections import defaultdict, deque
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
PROFILE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
SPEC = importlib.util.spec_from_file_location("ring_profile_l37", PROFILE_PATH)
PROFILE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(PROFILE)
# Frozen from player scale before measuring the L37 candidate. The 0.75 m line limit
# is just over the 0.68 m capsule width; 0.50 m2 is one capsule-width footprint.
STRAIGHT_LIMIT_M = 0.75
PLANE_LIMIT_M2 = 0.50
CORNER_TURN_LIMIT_DEG = 30.0
PLANE_TOLERANCE_M = 0.02
NORMAL_BUCKET = 0.07
PLANE_BUCKET_M = 0.03
MOUTH_CORRIDOR_HALF_WIDTH_M = 1.50
MOUTH_CORRIDOR_LENGTH_M = 12.0
MOUTH_Z_BELOW_M = 4.0
MOUTH_Z_ABOVE_M = 3.0
PLAYER_FLOOR_OFFSET_M = 1.13
DELTA_THRESHOLD_M = 0.05
DELTA_GRID_M = 0.125
SCENES = {
"owner-fi": ("owner-fi-ground-mouth.vfmesh", "layout-owner-fi.json", "FloatingIslands", "upper"),
"owner-sw": ("owner-sw-ground-mouth.vfmesh", "layout-owner-sw.json", "SurfaceWorld", "lower"),
"upper-sw-fixture": ("upper-sw-fixture-ground-mouth.vfmesh",
"layout-upper-sw-fixture.json", "SurfaceWorld", "upper"),
}
def sub(a, b):
return tuple(a[i] - b[i] for i in range(3))
def dot(a, b):
return sum(a[i] * b[i] for i in range(3))
def cross(a, b):
return (a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0])
def length(a):
return math.sqrt(dot(a, a))
def face(points):
n = cross(sub(points[1], points[0]), sub(points[2], points[0]))
twice_area = length(n)
if twice_area <= 1.0e-12:
return None
area = twice_area * 0.5
n = tuple(v / twice_area for v in n)
if n[2] < 0.0:
n = tuple(-v for v in n)
d = -dot(n, points[0])
return n, d, area
def vertex_key(point):
return tuple(round(value, 4) for value in point)
def edge_key(a, b):
ka, kb = vertex_key(a), vertex_key(b)
return (ka, kb) if ka < kb else (kb, ka)
def rdp(points, epsilon):
if len(points) <= 2:
return points
a, b = points[0], points[-1]
ab = sub(b, a)
ab2 = dot(ab, ab)
maximum, index = -1.0, -1
for i in range(1, len(points) - 1):
ap = sub(points[i], a)
t = max(0.0, min(1.0, dot(ap, ab) / ab2)) if ab2 else 0.0
closest = tuple(a[j] + t * ab[j] for j in range(3))
distance = length(sub(points[i], closest))
if distance > maximum:
maximum, index = distance, i
if maximum <= epsilon:
return [a, b]
left = rdp(points[:index + 1], epsilon)
right = rdp(points[index:], epsilon)
return left[:-1] + right
def simplified_boundary(component, records):
counts = defaultdict(int)
actual_edges = {}
for index in component:
points = records[index]["points"]
for a, b in ((points[0], points[1]), (points[1], points[2]),
(points[2], points[0])):
key = edge_key(a, b)
counts[key] += 1
actual_edges[key] = (key[0], key[1])
boundary = [edge for edge, count in counts.items() if count == 1]
graph = defaultdict(list)
for a, b in boundary:
graph[a].append(b)
graph[b].append(a)
if not graph:
return [], []
def direction(a, b):
dx, dy = b[0] - a[0], b[1] - a[1]
magnitude = math.hypot(dx, dy)
return ((dx / magnitude, dy / magnitude), magnitude) if magnitude > 1.0e-8 else (None, 0.0)
def straight_run(origin, first):
prior, current = origin, first
initial, edge_length = direction(prior, current)
if initial is None:
return 0.0
total = edge_length
seen = {origin, first}
while current in graph and len(graph[current]) == 2:
choices = [value for value in graph[current] if value != prior]
if len(choices) != 1 or choices[0] in seen:
break
nxt = choices[0]
outgoing, edge_length = direction(current, nxt)
if outgoing is None:
break
deflection = math.degrees(math.acos(max(-1.0, min(1.0,
initial[0] * outgoing[0] + initial[1] * outgoing[1]))))
if deflection > 8.0:
break
total += edge_length
prior, current = current, nxt
initial = outgoing
seen.add(current)
return total
longest = 0.0
corners = []
for vertex, neighbours in graph.items():
if len(neighbours) == 2:
incoming, _ = direction(neighbours[0], vertex)
outgoing, _ = direction(vertex, neighbours[1])
if incoming and outgoing:
turn = math.degrees(math.acos(max(-1.0, min(1.0,
incoming[0] * outgoing[0] + incoming[1] * outgoing[1]))))
if turn >= CORNER_TURN_LIMIT_DEG:
left_run = straight_run(vertex, neighbours[0])
right_run = straight_run(vertex, neighbours[1])
if left_run >= STRAIGHT_LIMIT_M and right_run >= STRAIGHT_LIMIT_M:
corners.append(turn)
for neighbour in neighbours:
longest = max(longest, straight_run(vertex, neighbour))
return [longest], corners
def analyze_triangles(triangles, center, center_z, crop_to_center=True,
include_sides=False):
records = []
for lod, average_nz, vertices in triangles:
points = tuple(vertex[0] for vertex in vertices)
centroid = tuple(sum(point[axis] for point in points) / 3.0 for axis in range(3))
if (crop_to_center and
(abs(centroid[0] - center[0]) > MOUTH_CORRIDOR_LENGTH_M + 2.0
or abs(centroid[1] - center[1]) > MOUTH_CORRIDOR_LENGTH_M + 2.0
or centroid[2] < center_z - MOUTH_Z_BELOW_M
or centroid[2] > center_z + MOUTH_Z_ABOVE_M)):
continue
measured = face(points)
if not measured:
continue
normal, plane_d, area = measured
if not include_sides and (normal[2] < 0.35 or average_nz < 0.25):
continue
bucket = (round(normal[0] / NORMAL_BUCKET), round(normal[1] / NORMAL_BUCKET),
round(normal[2] / NORMAL_BUCKET), round(plane_d / PLANE_BUCKET_M))
records.append({"points": points, "centroid": centroid, "normal": normal,
"d": plane_d, "area": area, "bucket": bucket})
by_bucket = defaultdict(list)
for index, record in enumerate(records):
by_bucket[record["bucket"]].append(index)
edge_indices = defaultdict(list)
for index, record in enumerate(records):
points = record["points"]
for a, b in ((points[0], points[1]), (points[1], points[2]),
(points[2], points[0])):
edge_indices[edge_key(a, b)].append(index)
adjacency = defaultdict(list)
for indices in edge_indices.values():
if len(indices) == 2:
a, b = indices
# Adjacent triangles can differ by one normal bucket on a smooth mesh.
# Plane membership is checked against the seed below, so let that test
# decide whether a connected patch is planar instead of splitting it at
# the bucket boundary.
adjacency[a].append(b)
adjacency[b].append(a)
max_area = 0.0
max_straight = 0.0
corner_count = 0
max_corner_turn = 0.0
max_bounds = None
max_normal = None
visited = set()
for indices in by_bucket.values():
for seed in indices:
if seed in visited:
continue
component = []
queue = deque([seed])
queued = {seed}
seed_record = records[seed]
while queue:
index = queue.popleft()
record = records[index]
if (abs(dot(seed_record["normal"], record["normal"])) < math.cos(math.radians(4.5))
or max(abs(dot(seed_record["normal"], point) + seed_record["d"])
for point in record["points"]) > PLANE_TOLERANCE_M):
continue
visited.add(index)
component.append(index)
for neighbour in adjacency[index]:
if neighbour not in visited and neighbour not in queued:
queued.add(neighbour)
queue.append(neighbour)
area = sum(records[index]["area"] for index in component)
if area <= max_area:
continue
segments, corners = simplified_boundary(component, records)
max_area = area
max_straight = max(segments, default=0.0)
corner_count = len(corners)
max_corner_turn = max(corners, default=0.0)
points = [point for index in component for point in records[index]["points"]]
max_bounds = tuple((min(point[axis] for point in points),
max(point[axis] for point in points)) for axis in range(3))
max_normal = seed_record["normal"]
return {"triangles": len(records), "planar_area": max_area,
"straight": max_straight, "corners": corner_count,
"corner_turn_max": max_corner_turn,
"bounds": max_bounds, "normal": max_normal}
def synthetic_box(center, z):
x, y = center
corners = ((x - 1.5, y - 1.0, z), (x + 1.5, y - 1.0, z),
(x + 1.5, y + 1.0, z), (x - 1.5, y + 1.0, z))
return [(0, 1.0, tuple((point, (0.0, 0.0, 1.0)) for point in tri))
for tri in ((corners[0], corners[1], corners[2]),
(corners[0], corners[2], corners[3]))]
def fail(metrics, include_planarity=True):
planar_fail = (include_planarity and
(metrics["planar_area"] >= PLANE_LIMIT_M2
or metrics.get("all_face_planar_area", 0.0) >= PLANE_LIMIT_M2))
return (planar_fail
or metrics["straight"] > STRAIGHT_LIMIT_M
or metrics.get("all_face_straight", 0.0) > STRAIGHT_LIMIT_M
or metrics["corners"] > 0
or metrics.get("all_face_corners", 0) > 0)
def mouth_route_points(passage, side, scale):
controls = [
(tuple(value * scale for value in point["position_q"]),
point["radius_q"] * scale)
for point in passage["control_points"]
]
if side == "upper":
return controls[:4]
lower_mouth = tuple(value * scale for value in passage["lower_mouth_q"])
lower_index = min(range(len(controls)), key=lambda index: sum(
(controls[index][0][axis] - lower_mouth[axis]) ** 2 for axis in range(3)))
return list(reversed(controls[max(0, lower_index - 3):lower_index + 1]))
def select_corridor_triangles(triangles, route, half_width, path_length):
selected = []
for triangle in triangles:
vertices = triangle[2]
centroid = tuple(sum(vertex[0][axis] for vertex in vertices) / 3.0
for axis in range(3))
closest_distance = math.inf
closest_along = 0.0
closest_floor = 0.0
prefix = 0.0
for (a, radius_a), (b, radius_b) in zip(route, route[1:]):
dx, dy, dz = (b[axis] - a[axis] for axis in range(3))
horizontal_length_squared = dx * dx + dy * dy
horizontal_length = math.sqrt(horizontal_length_squared)
if horizontal_length_squared <= 1.0e-12:
t = 0.0
else:
t = max(0.0, min(1.0,
((centroid[0] - a[0]) * dx + (centroid[1] - a[1]) * dy)
/ horizontal_length_squared))
center_x, center_y = a[0] + t * dx, a[1] + t * dy
lateral = math.hypot(centroid[0] - center_x, centroid[1] - center_y)
segment_length = math.sqrt(horizontal_length_squared + dz * dz)
if lateral < closest_distance:
closest_distance = lateral
closest_along = prefix + t * segment_length
center_z = a[2] + t * dz
radius = radius_a + t * (radius_b - radius_a)
closest_floor = center_z - radius + PLAYER_FLOOR_OFFSET_M
prefix += segment_length
if (closest_distance <= half_width
and 0.0 <= closest_along <= path_length
and abs(centroid[2] - closest_floor) <= 1.5):
selected.append(triangle)
return selected
def route_floor_at(route, x, y):
closest = None
prefix = 0.0
for (a, radius_a), (b, radius_b) in zip(route, route[1:]):
dx, dy, dz = (b[axis] - a[axis] for axis in range(3))
horizontal_sq = dx * dx + dy * dy
t = max(0.0, min(1.0, ((x - a[0]) * dx + (y - a[1]) * dy)
/ horizontal_sq)) if horizontal_sq > 1.0e-12 else 0.0
center_x, center_y = a[0] + t * dx, a[1] + t * dy
lateral = math.hypot(x - center_x, y - center_y)
length = math.sqrt(horizontal_sq + dz * dz)
along = prefix + t * length
center_z = a[2] + t * dz
radius = radius_a + t * (radius_b - radius_a)
floor = center_z - radius + PLAYER_FLOOR_OFFSET_M
candidate = (lateral, along, floor)
if closest is None or lateral < closest[0]:
closest = candidate
prefix += length
if closest and closest[0] <= MOUTH_CORRIDOR_HALF_WIDTH_M \
and closest[1] <= MOUTH_CORRIDOR_LENGTH_M:
return closest[2]
return None
def differential_surface(candidate_triangles, baseline_triangles, route, bounds):
cell_size = 0.5
candidate_buckets = PROFILE.build_buckets(candidate_triangles, True, bounds, cell_size)
baseline_buckets = PROFILE.build_buckets(baseline_triangles, True, bounds, cell_size)
route_x = [point[0][0] for point in route]
route_y = [point[0][1] for point in route]
padding = MOUTH_CORRIDOR_HALF_WIDTH_M + DELTA_GRID_M
x0 = math.floor((min(route_x) - padding) / DELTA_GRID_M) * DELTA_GRID_M
x1 = math.ceil((max(route_x) + padding) / DELTA_GRID_M) * DELTA_GRID_M
y0 = math.floor((min(route_y) - padding) / DELTA_GRID_M) * DELTA_GRID_M
y1 = math.ceil((max(route_y) + padding) / DELTA_GRID_M) * DELTA_GRID_M
nx = round((x1 - x0) / DELTA_GRID_M)
ny = round((y1 - y0) / DELTA_GRID_M)
samples = {}
for iy in range(ny + 1):
y = y0 + iy * DELTA_GRID_M
for ix in range(nx + 1):
x = x0 + ix * DELTA_GRID_M
floor = route_floor_at(route, x, y)
if floor is None:
continue
candidate = PROFILE.sample_surface(candidate_buckets, x, y, True,
cell_size, preferred_height=floor)
baseline = PROFILE.sample_surface(baseline_buckets, x, y, True,
cell_size, preferred_height=floor)
samples[(ix, iy)] = (candidate, baseline)
delta_triangles = []
changed_cells = 0
changed_area = 0.0
deltas = []
for iy in range(ny):
for ix in range(nx):
keys = ((ix, iy), (ix + 1, iy), (ix + 1, iy + 1), (ix, iy + 1))
values = [samples.get(key) for key in keys]
if any(value is None or value[0] is None for value in values):
continue
cell_deltas = []
changed = False
for candidate, baseline in values:
if baseline is None:
changed = True
cell_deltas.append(math.inf)
else:
delta = candidate[0] - baseline[0]
cell_deltas.append(abs(delta))
deltas.append(abs(delta))
changed |= abs(delta) >= DELTA_THRESHOLD_M
if not changed:
continue
changed_cells += 1
changed_area += DELTA_GRID_M * DELTA_GRID_M
points = []
for key, (candidate, _) in zip(keys, values):
x = x0 + key[0] * DELTA_GRID_M
y = y0 + key[1] * DELTA_GRID_M
points.append(((x, y, candidate[0]), candidate[1]))
for triangle_indices in ((0, 1, 2), (0, 2, 3)):
vertices = tuple(points[index] for index in triangle_indices)
avg_nz = sum(vertex[1][2] for vertex in vertices) / 3.0
delta_triangles.append((0, avg_nz, vertices))
metrics = analyze_triangles(delta_triangles, (0.0, 0.0), 0.0,
crop_to_center=False)
metrics["changed_cells"] = changed_cells
metrics["changed_area"] = changed_area
metrics["max_delta"] = max(deltas, default=0.0)
metrics["mean_abs_delta"] = sum(deltas) / len(deltas) if deltas else 0.0
return metrics
def audit_scene(scene, out, baseline_out, mesh_override=None):
mesh_name, layout_name, target_generator, side = SCENES[scene]
import json
layout = json.loads((out / layout_name).read_text(encoding="utf-8"))
scale = layout["core_voxel_size_um"] / (256.0 * 1_000_000.0)
strata = {int(value["strate_index"]): value for value in layout["strates"]}
choices = []
for passage in layout["passages"]:
side_index = int(passage[side + "_strate_index"])
if strata[side_index]["generator_kind"] == target_generator:
choices.append(passage)
if not choices:
raise RuntimeError(f"no {target_generator} {side} mouth in {scene}")
passage = min(choices, key=lambda value: value["stable_id"])
mouth = tuple(value * scale for value in passage[f"{side}_mouth_q"])
bounds = (mouth[0] - MOUTH_CORRIDOR_LENGTH_M - MOUTH_CORRIDOR_HALF_WIDTH_M - 1.0,
mouth[0] + MOUTH_CORRIDOR_LENGTH_M + MOUTH_CORRIDOR_HALF_WIDTH_M + 1.0,
mouth[1] - MOUTH_CORRIDOR_LENGTH_M - MOUTH_CORRIDOR_HALF_WIDTH_M - 1.0,
mouth[1] + MOUTH_CORRIDOR_LENGTH_M + MOUTH_CORRIDOR_HALF_WIDTH_M + 1.0)
voxel_um, triangles, tiles = PROFILE.read_scene(
str(mesh_override or out / mesh_name), 0,
mouth[2] - MOUTH_Z_BELOW_M, mouth[2] + MOUTH_Z_ABOVE_M, bounds)
baseline_um, baseline_triangles, baseline_tiles = PROFILE.read_scene(
str(baseline_out / mesh_name), 0, mouth[2] - MOUTH_Z_BELOW_M,
mouth[2] + MOUTH_Z_ABOVE_M, bounds)
if voxel_um != baseline_um:
raise RuntimeError(f"{scene} candidate/control voxel size differs")
route = mouth_route_points(passage, side, scale)
corridor = select_corridor_triangles(triangles, route,
MOUTH_CORRIDOR_HALF_WIDTH_M, MOUTH_CORRIDOR_LENGTH_M)
baseline_corridor = select_corridor_triangles(baseline_triangles, route,
MOUTH_CORRIDOR_HALF_WIDTH_M, MOUTH_CORRIDOR_LENGTH_M)
differential = differential_surface(corridor, baseline_corridor, route, bounds)
metrics = analyze_triangles(corridor, mouth[:2], mouth[2], crop_to_center=False)
baseline_metrics = analyze_triangles(baseline_corridor, mouth[:2], mouth[2],
crop_to_center=False)
full_surface = analyze_triangles(corridor, mouth[:2], mouth[2],
crop_to_center=False, include_sides=True)
metrics["all_face_planar_area"] = full_surface["planar_area"]
metrics["all_face_straight"] = full_surface["straight"]
metrics["all_face_corners"] = full_surface["corners"]
metrics["all_face_corner_turn_max"] = full_surface["corner_turn_max"]
print(f"[MouthBuiltShape] scene={scene} mesh={mesh_name} mouth_m=({mouth[0]:.3f},"
f"{mouth[1]:.3f},{mouth[2]:.3f}) voxel_um={voxel_um} tiles={tiles} "
f"route_corridor_m=({MOUTH_CORRIDOR_HALF_WIDTH_M:.2f}m_halfwidth,"
f"{MOUTH_CORRIDOR_LENGTH_M:.1f}m_length) corridor_triangles={len(corridor)} "
f"surface_triangles={metrics['triangles']} planar_patch_m2={metrics['planar_area']:.4f} "
f"straight_run_m={metrics['straight']:.4f} sharp_corners={metrics['corners']} "
f"corner_turn_max_deg={metrics['corner_turn_max']:.2f} "
f"all_face_planar_patch_m2={full_surface['planar_area']:.4f} "
f"all_face_straight_run_m={full_surface['straight']:.4f} "
f"all_face_corners={full_surface['corners']} "
f"all_face_corner_turn_max_deg={full_surface['corner_turn_max']:.2f} "
f"matched_no_support_planar_patch_m2={baseline_metrics['planar_area']:.4f} "
f"matched_no_support_straight_run_m={baseline_metrics['straight']:.4f} "
f"matched_no_support_corners={baseline_metrics['corners']} "
f"control_tiles={baseline_tiles} changed_cells={differential['changed_cells']} "
f"changed_area_m2={differential['changed_area']:.4f} "
f"max_abs_delta_m={differential['max_delta']:.4f} "
f"mean_abs_delta_m={differential['mean_abs_delta']:.4f} "
f"added_planar_patch_m2={differential['planar_area']:.4f} "
f"added_straight_run_m={differential['straight']:.4f} "
f"added_sharp_corners={differential['corners']} "
f"patch_bounds={metrics['bounds']} plane_normal={metrics['normal']} "
f"limits=(plane<{PLANE_LIMIT_M2:.2f}m2 informational,line<={STRAIGHT_LIMIT_M:.2f}m,"
f"corner_turn<{CORNER_TURN_LIMIT_DEG:.0f}deg) "
f"delta_threshold_m={DELTA_THRESHOLD_M:.2f} status="
f"{'FAIL' if fail(differential, include_planarity=False) else 'PASS'}")
metrics["differential"] = differential
metrics["matched_no_support"] = baseline_metrics
return metrics
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", type=Path, default=ROOT / "Saved/Linux/l37")
parser.add_argument("--baseline-out", type=Path,
default=ROOT / "Saved/Linux/l37/no-tread-control")
parser.add_argument("--control-out", type=Path, default=ROOT / "Saved/Linux/l36")
parser.add_argument("--scene", choices=tuple(SCENES) + ("all",), default="all")
args = parser.parse_args()
out = args.out if args.out.is_absolute() else ROOT / args.out
baseline_out = (args.baseline_out if args.baseline_out.is_absolute()
else ROOT / args.baseline_out)
control_out = (args.control_out if args.control_out.is_absolute()
else ROOT / args.control_out)
scenes = tuple(SCENES) if args.scene == "all" else (args.scene,)
measured = {scene: audit_scene(scene, out, baseline_out) for scene in scenes}
l36_fi = audit_scene("owner-fi", control_out, baseline_out)
l36_failed = fail(l36_fi["differential"])
print(f"[MouthBuiltShapeL36Control] scene=owner-fi planar_patch_m2="
f"{l36_fi['differential']['planar_area']:.4f} "
f"straight_run_m={l36_fi['differential']['straight']:.4f} "
f"sharp_corners={l36_fi['differential']['corners']} detected={l36_failed} "
f"negative_control={'PASS' if l36_failed else 'FAIL'}")
control = analyze_triangles(synthetic_box((0.0, 0.0), 0.0), (0.0, 0.0), 0.0)
control_ok = fail(control)
print(f"[MouthBuiltShapeBoxControl] dimensions_m=3x2 planar_patch_m2="
f"{control['planar_area']:.4f} straight_run_m={control['straight']:.4f} "
f"sharp_corners={control['corners']} detected={control_ok} "
f"negative_control={'PASS' if control_ok else 'FAIL'}")
candidates_pass = all(not fail(metrics["differential"], include_planarity=False)
for metrics in measured.values())
passed = control_ok and l36_failed and candidates_pass
print(f"[MouthBuiltShapeSummary] scenes={len(measured)} candidates_pass="
f"{sum(not fail(metrics['differential']) for metrics in measured.values())}/{len(measured)} "
f"L36_control_fail={'PASS' if l36_failed else 'FAIL'} "
f"box_control_fail={'PASS' if control_ok else 'FAIL'} "
f"planar_gate=informational_owner_accepted "
f"status={'PASS' if passed else 'FAIL'}")
raise SystemExit(0 if passed else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Render the fixed 17 m FI mouth overview as a small JPEG."""
import math
import os
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = Path(os.environ.get("VOXELFORGE_L37_OUT", ROOT / "Saved/Linux/l37"))
CAPTURES = Path(os.environ.get("VOXELFORGE_L37_CAPTURE_DIR",
Path(__file__).resolve().parent / "captures"))
MESH = Path(os.environ.get("VOXELFORGE_L37_MESH",
OUT / "owner-fi-ground-mouth.vfmesh"))
RENDERER = Path(os.environ.get("VOXELFORGE_L37_RENDERER",
OUT / "bin/VoxelForgeOffscreenCapture-l37-final-clang"))
PNG = OUT / "capture-png/owner-fi-ground-mouth-overview.png"
JPEG = CAPTURES / "owner-fi-ground-mouth-overview.jpg"
MOUTH = (5.600, 0.445, -55.156)
CAMERA = (MOUTH[0] - 9.75, MOUTH[1] - 11.4, MOUTH[2] + 8.0)
TARGET = (6.298233, 1.137233, -55.468264)
FOV = 80.0
def normalize(vector):
magnitude = math.sqrt(sum(value * value for value in vector))
return tuple(value / magnitude for value in vector)
def camera_angles(camera, target):
delta = tuple(target[i] - camera[i] for i in range(3))
horizontal = math.hypot(delta[0], delta[1])
return math.degrees(math.atan2(delta[0], delta[1])), math.degrees(
math.atan2(delta[2], horizontal))
def fmt(values):
return ",".join(f"{value:.6f}" for value in values)
def run(name, command):
log = OUT / f"capture-overview-{name}.log"
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", *map(str, command)]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
elapsed = time.monotonic() - started
print(f"[L37OverviewCaptureRun] step={name} elapsed={elapsed:.1f}s "
f"exit={result.returncode} log={log}", flush=True)
if result.returncode != 0:
raise SystemExit(f"{name} failed; see {log}")
def main():
if not MESH.is_file():
raise SystemExit(f"missing FI mouth mesh: {MESH}")
for directory in (OUT, PNG.parent, CAPTURES):
directory.mkdir(parents=True, exist_ok=True)
yaw, pitch = camera_angles(CAMERA, TARGET)
run("render", [RENDERER, MESH, PNG, "--size=1600x900", f"--fov-deg={FOV}",
f"--camera-m={fmt(CAMERA)}", f"--yaw-deg={yaw:.6f}",
f"--pitch-deg={pitch:.6f}", "--tile-skirts=off", "--shade=readable",
"--headlamp=on", "--height-tint=on",
f"--height-center-m={TARGET[2]:.6f}", "--height-range-m=8",
"--tile-edges=off", f"--focus-region={fmt((*MOUTH, 20.0))}"])
run("jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error",
"-y", "-i", PNG, "-frames:v", "1", "-q:v", "4", JPEG])
size = JPEG.stat().st_size if JPEG.exists() else 0
distance = math.dist(CAMERA, MOUTH)
print(f"[L37OverviewCapture] file={JPEG.relative_to(ROOT)} camera_to_mouth_m="
f"{distance:.3f} camera_m=({fmt(CAMERA)}) target_m=({fmt(TARGET)}) "
f"fov_deg={FOV:.1f} resolution=1600x900 bytes={size} "
f"status={'PASS' if 0 < size <= 300_000 else 'FAIL'}", flush=True)
raise SystemExit(0 if 0 < size <= 300_000 else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,404 @@
#!/usr/bin/env python3
"""Export and render the standard four-view passage set for L37."""
import json
import importlib.util
import argparse
import math
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = Path(os.environ.get("VOXELFORGE_L37_OUT", ROOT / "Saved/Linux/l37"))
BIN = OUT / "bin"
CAPTURES = Path(os.environ.get("VOXELFORGE_L37_CAPTURE_DIR",
Path(__file__).resolve().parent / "captures"))
HEADLESS = Path(os.environ.get("VOXELFORGE_L37_HEADLESS",
BIN / "VoxelForgeCoreHeadless-l37-final-clang"))
RENDERER = Path(os.environ.get("VOXELFORGE_L37_RENDERER",
BIN / "VoxelForgeOffscreenCapture-l37-final-clang"))
PNG_DIR = OUT / "capture-png"
RECIPE_INPUTS = (
("owner-fi", ROOT / "Saved/Linux/linux25-owner-floating-islands-style0.json",
"floating-islands", "FloatingIslands", "upper"),
("owner-sw", ROOT / "Saved/Linux/linux25-owner-surface-world-style0.json",
"surface-world", "SurfaceWorld", "lower"),
("upper-sw-fixture", ROOT / "Saved/Linux/l33/fixtures/sw-upper-tunnel-network-style0.json",
"floating-islands", "SurfaceWorld", "upper"),
)
SUBVOXELS_PER_VOXEL = 256.0
RING_AUDIT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
spec = importlib.util.spec_from_file_location("ring_profile_l32", RING_AUDIT)
ring_profile = importlib.util.module_from_spec(spec)
spec.loader.exec_module(ring_profile)
for directory in (OUT, BIN, PNG_DIR, CAPTURES):
directory.mkdir(parents=True, exist_ok=True)
ENV = os.environ.copy()
ENV["LP_NUM_THREADS"] = "12"
ENV["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
PARSER = argparse.ArgumentParser(description=__doc__)
PARSER.add_argument("--scene", choices=[value[0] for value in RECIPE_INPUTS])
PARSER.add_argument("--view", choices=("ground-mouth", "rock-entry", "mid-tunnel", "arrival"))
OPTIONS = PARSER.parse_args()
def run(name, command):
log = OUT / f"capture-{name}.log"
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", *map(str, command)]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, env=ENV, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
elapsed = time.monotonic() - started
print(f"[L37CaptureRun] step={name} elapsed={elapsed:.1f}s exit={result.returncode} log={log}",
flush=True)
if result.returncode != 0:
raise SystemExit(f"{name} failed; see {log}")
return log
def add(a, b):
return tuple(x + y for x, y in zip(a, b))
def sub(a, b):
return tuple(x - y for x, y in zip(a, b))
def mul(a, scale):
return tuple(x * scale for x in a)
def norm(a):
length = math.sqrt(sum(x * x for x in a))
if length <= 1.0e-9:
raise ValueError("passage has a degenerate camera tangent")
return tuple(x / length for x in a)
def interpolate_route(points, distance):
remaining = max(0.0, distance)
for a, b in zip(points, points[1:]):
delta = sub(b[0], a[0])
length = math.sqrt(sum(x * x for x in delta))
if length <= 1.0e-9:
continue
if remaining <= length:
t = remaining / length
pos = tuple(a[0][axis] + delta[axis] * t for axis in range(3))
radius = a[1] + (b[1] - a[1]) * t
return pos, radius
remaining -= length
return points[-1]
def route_frame(points, distance):
center, radius = interpolate_route(points, distance)
before, _ = interpolate_route(points, max(0.0, distance - 0.25))
after, _ = interpolate_route(points, distance + 0.25)
tangent = norm(sub(after, before))
horizontal = norm((tangent[0], tangent[1], 0.0))
side = (-horizontal[1], horizontal[0], 0.0)
return center, radius, tangent, side
def camera_angles(camera, target):
delta = sub(target, camera)
horizontal = math.hypot(delta[0], delta[1])
yaw = math.degrees(math.atan2(delta[0], delta[1]))
pitch = math.degrees(math.atan2(delta[2], horizontal))
return yaw, pitch
def fmt(values):
return ",".join(f"{value:.6f}" for value in values)
def load_reference_cameras():
log = ROOT / "Saved/Linux/l34/run-capture-standard-set-final-radius3.log"
pattern = re.compile(
r"^\[L34CaptureCamera\] view=(\S+).*?camera_m=\(([^)]*)\) "
r"target_m=\(([^)]*)\) clip=(\S+)", re.M)
values = {}
for name, camera, target, clip in pattern.findall(
log.read_text(encoding="utf-8", errors="replace")):
parse = lambda value: tuple(float(part) for part in value.split(","))
values[name] = (parse(camera), parse(target), None if clip == "none" else parse(clip))
return values
REFERENCE_CAMERAS = load_reference_cameras()
def terrain_surface_near(mesh, x, y, expected_height):
bounds = (x - 2.0, x + 2.0, y - 2.0, y + 2.0)
_, triangles, _ = ring_profile.read_scene(
str(mesh), None, -1.0e9, 1.0e9, bounds)
buckets = ring_profile.build_buckets(triangles, True, bounds, cell_size=1.0)
hits = []
for dy in range(-4, 5):
for dx in range(-4, 5):
sample_x, sample_y = x + dx * 0.5, y + dy * 0.5
hit = ring_profile.sample_surface(buckets, sample_x, sample_y, True, 1.0,
preferred_height=expected_height)
if hit is not None:
hits.append((math.hypot(dx * 0.5, dy * 0.5),
abs(hit[0] - expected_height), sample_x, sample_y, hit[0]))
if not hits:
return None
distance, _, sample_x, sample_y, height = min(hits)
if distance > 2.0 or abs(height - expected_height) > 8.0:
return None
return sample_x, sample_y, height
def load_layout(name, source, recipe):
path = OUT / f"layout-{name}.json"
run(f"layout-{name}", [HEADLESS, source, "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=2",
"--max-clip-level=0", "--max-level=0", f"--recipe={recipe}",
f"--resolved-layout-output={path}", "--resolved-layout-only"])
return json.loads(path.read_text(encoding="utf-8"))
def select_passage(layout, mouth_generator, preferred_side):
strata = {int(value["strate_index"]): value for value in layout["strates"]}
preferred_candidates = []
fallback_candidates = []
for passage in layout["passages"]:
upper = strata[int(passage["upper_strate_index"])]
lower = strata[int(passage["lower_strate_index"])]
sides = []
if upper["generator_kind"] == mouth_generator:
sides.append("upper")
if lower["generator_kind"] == mouth_generator:
sides.append("lower")
if preferred_side in sides:
preferred_candidates.append((passage, preferred_side == "lower"))
elif sides:
fallback_candidates.append((passage, sides[0] == "lower"))
candidates = preferred_candidates or fallback_candidates
if not candidates:
raise RuntimeError(f"no passage mouth in generator {mouth_generator}")
return sorted(candidates, key=lambda item: item[0]["stable_id"])[0]
def make_views(layout, passage, reverse):
q_to_m = layout["core_voxel_size_um"] / (SUBVOXELS_PER_VOXEL * 1_000_000.0)
controls = [
(tuple(value * q_to_m for value in point["position_q"]),
point["radius_q"] * q_to_m)
for point in passage["control_points"]
]
lower_mouth = tuple(value * q_to_m for value in passage["lower_mouth_q"])
lower_index = min(range(len(controls)), key=lambda i: sum(
(controls[i][0][axis] - lower_mouth[axis]) ** 2 for axis in range(3)))
path = controls[:lower_index + 1]
if len(path) < 2:
raise RuntimeError(f"{passage['stable_id']} has no mouth-to-mouth route")
if reverse:
path.reverse()
lengths = [math.sqrt(sum((b[0][axis] - a[0][axis]) ** 2 for axis in range(3)))
for a, b in zip(path, path[1:])]
total = sum(lengths)
upper, upper_radius, upper_tangent, upper_side = route_frame(path, 0.0)
lower, lower_radius, lower_tangent, lower_side = route_frame(path, total)
middle_distance = max(12.0, min(total - 12.0, total * 0.4)) if total > 24.0 else total * 0.4
entry_distance = min(6.0, total * 0.25)
entry, entry_radius, entry_tangent, entry_side = route_frame(path, entry_distance)
middle, middle_radius, middle_tangent, middle_side = route_frame(path, middle_distance)
def exterior_view(prefix, center, radius, outward, target):
horizontal_outward = norm((outward[0], outward[1], 0.0))
eye_height = center[2] - radius + 1.6
eye = (center[0] + horizontal_outward[0] * 5.5,
center[1] + horizontal_outward[1] * 5.5,
eye_height)
yaw, pitch = camera_angles(eye, target)
return {"name": prefix, "camera": eye, "yaw": yaw, "pitch": pitch,
"target": target, "clip": None, "camera_xy": eye[:2],
"focus": center, "mouth": center, "eye_height_z": eye_height}
entrance, entrance_radius, entrance_tangent = upper, upper_radius, upper_tangent
exit_point, exit_radius, exit_tangent = lower, lower_radius, lower_tangent
ground_target_pos, ground_target_radius = interpolate_route(path, min(total, 1.0))
ground = exterior_view("ground-mouth", entrance, entrance_radius,
mul(entrance_tangent, -1.0), (ground_target_pos[0], ground_target_pos[1],
ground_target_pos[2] - ground_target_radius + 1.0))
ground["focus"] = entrance
def cutaway_view(prefix, center, radius, tangent, side, distance, target_distance):
camera = (center[0] - side[0] * 7.0, center[1] - side[1] * 7.0,
center[2])
target_pos, target_radius = interpolate_route(path, target_distance)
target = (target_pos[0], target_pos[1], target_pos[2])
yaw, pitch = camera_angles(camera, target)
plane = (side[0], side[1], 0.0,
-(side[0] * center[0] + side[1] * center[1]))
return {"name": prefix, "camera": camera, "yaw": yaw, "pitch": pitch,
"target": target, "clip": plane, "camera_xy": camera[:2],
"focus": center, "mouth": center}
# The lower-SW fixture's terminal volume has no LOD0 solid surface to cap.
# Use the far-side, fully meshed section for a comparable capped arrival view.
if passage["stable_id"] == "passage/1-2/0":
arrival_distance = total * 0.45
elif passage["stable_id"] == "passage/0-1/0" and reverse:
# Keep this SurfaceWorld arrival view near the endpoint while avoiding
# the tile boundary that left an open contour at the final 14 m.
arrival_distance = total * 0.80
else:
arrival_distance = max(0.0, total - 14.0)
arrival_center, arrival_radius, arrival_tangent, arrival_side = route_frame(
path, arrival_distance)
arrival = cutaway_view("arrival", arrival_center, arrival_radius,
arrival_tangent, arrival_side, arrival_distance,
min(total, arrival_distance + 12.0))
arrival_camera, _ = interpolate_route(path, max(0.0, arrival_distance - 6.0))
arrival["camera"] = arrival_camera
arrival_cut_point = (arrival_center[0] - arrival_side[0] * 2.5,
arrival_center[1] - arrival_side[1] * 2.5, arrival_center[2])
arrival["clip"] = (*arrival_side,
-sum(arrival_side[axis] * arrival_cut_point[axis] for axis in range(3)))
arrival["camera_location"] = "passage-air"
arrival["focus_radius"] = 20.0
arrival["yaw"], arrival["pitch"] = camera_angles(
arrival["camera"], arrival["target"])
entry_camera_distance = min(5.5, total * 0.1)
entry_center, entry_radius, entry_tangent, entry_side = route_frame(
path, entry_camera_distance)
# Exterior oblique view at eye height, aimed along the portal blend.
outward_horizontal = norm((-upper_tangent[0], -upper_tangent[1], 0.0))
entry_camera = (entrance[0] + outward_horizontal[0] * 4.5 - upper_side[0] * 2.5,
entrance[1] + outward_horizontal[1] * 4.5 - upper_side[1] * 2.5,
entrance[2] - upper_radius + 1.6)
entry_target_pos, entry_target_radius = interpolate_route(
path, min(total, 2.0))
entry_target = (entry_target_pos[0], entry_target_pos[1],
entry_target_pos[2] - entry_target_radius + 2.5)
entry_yaw, entry_pitch = camera_angles(entry_camera, entry_target)
entry_view = {"name": "rock-entry", "camera": entry_camera,
"yaw": entry_yaw, "pitch": entry_pitch, "target": entry_target,
"clip": None, "camera_xy": entry_camera[:2],
"focus": entry_center, "mouth": entrance, "outside_camera": True,
"camera_location": "exterior", "focus_radius": None,
"eye_height_z": entry_camera[2]}
middle_view = cutaway_view("mid-tunnel", middle, middle_radius, middle_tangent,
middle_side, middle_distance, min(total, middle_distance + 2.0))
ground["focus_radius"] = 20.0
middle_view["focus_radius"] = 20.0
middle_camera, _ = interpolate_route(path, max(0.0, middle_distance - 6.0))
middle_view["camera"] = middle_camera
middle_target_pos, _ = interpolate_route(path, min(total, middle_distance + 9.0))
middle_view["target"] = (middle_target_pos[0], middle_target_pos[1], middle[2])
# The camera sits in the passage air. Remove the near wall to open a
# longitudinal section and cap the remaining rock at that cut.
middle_cut_point = (middle[0] - middle_side[0] * 2.5,
middle[1] - middle_side[1] * 2.5, middle[2])
middle_view["clip"] = (*middle_side,
-sum(middle_side[axis] * middle_cut_point[axis] for axis in range(3)))
middle_view["camera_location"] = "passage-air"
middle_view["yaw"], middle_view["pitch"] = camera_angles(
middle_view["camera"], middle_view["target"])
return (ground, entry_view, middle_view, arrival), total, lower_index
for scene_name, source, recipe, mouth_generator, preferred_side in RECIPE_INPUTS:
if OPTIONS.scene and scene_name != OPTIONS.scene:
continue
layout = load_layout(scene_name, source, recipe)
passage, reverse = select_passage(layout, mouth_generator, preferred_side)
views, route_length, lower_index = make_views(layout, passage, reverse)
print(f"[L37StandardCaptureSet] scene={scene_name} recipe={recipe} passage={passage['stable_id']} "
f"route_m={route_length:.3f} control_points={lower_index + 1} fov_deg=80 "
"ground_camera_outside_m=5.500 entry_camera_outside_m=5.148 camera_eye_m=1.600", flush=True)
for view in views:
if OPTIONS.view and view["name"] != OPTIONS.view:
continue
stem = f"{scene_name}-{view['name']}"
mesh = OUT / f"{stem}.vfmesh"
png = PNG_DIR / f"{stem}.png"
jpg = CAPTURES / f"{stem}.jpg"
yaw, pitch = view["yaw"], view["pitch"]
camera = view["camera"]
reference_camera = REFERENCE_CAMERAS.get(stem)
if reference_camera is not None:
camera, target, reference_clip = reference_camera
view["camera"], view["target"], view["clip"] = camera, target, reference_clip
yaw, pitch = camera_angles(camera, target)
view["yaw"], view["pitch"] = yaw, pitch
camera_spec = (*camera, yaw, pitch)
export_camera = view.get("focus", camera)
export_clip_radius = 3 if scene_name == "owner-fi" and view["name"] == "mid-tunnel" else 2
run(f"{stem}-export", [HEADLESS, source, "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", f"--clip-radius={export_clip_radius}",
"--max-clip-level=0", "--max-level=0",
"--render-distance-chunks=0", f"--recipe={recipe}",
f"--camera={fmt((*export_camera, yaw, pitch))}", f"--mesh-export={mesh}"])
render_clip = view["clip"]
if reference_camera is not None:
surface = None
ground_source = "L34-fixed-camera"
elif view.get("outside_camera", False):
surface = terrain_surface_near(mesh, *view["camera_xy"], view["target"][2])
ground_source = "terrain-mesh"
if surface is None:
camera = (view["camera_xy"][0], view["camera_xy"][1],
view["eye_height_z"])
ground_source = "mouth-eye-height-fallback"
else:
camera = (surface[0], surface[1], surface[2] + 1.6)
elif view["clip"] is not None:
# The clip plane removes the rock half-space containing this side camera.
# Keep its height at the passage eye line so the cut section stays centered.
surface = None
camera = view["camera"]
ground_source = "clipped-passage-side-camera"
else:
surface = terrain_surface_near(mesh, *view["camera_xy"], view["target"][2])
ground_source = "terrain-mesh"
if surface is None:
surface_z = view.get("eye_height_z", view["target"][2] + 0.4)
camera = (view["camera_xy"][0], view["camera_xy"][1], surface_z)
ground_source = "mouth-eye-height-fallback"
else:
camera = (surface[0], surface[1], surface[2] + 1.6)
yaw, pitch = camera_angles(camera, view["target"])
mouth_position = view["mouth"][0:2]
camera_mouth_distance = math.dist(camera, view["mouth"])
ground_height = surface[2] if surface is not None else float("nan")
surface_offset = (surface[2] - view["target"][2]) if surface is not None else float("nan")
print(f"[L37CaptureCamera] view={stem} ground_source={ground_source} "
f"ground_z_m={ground_height:.3f} "
f"surface_target_delta_m={surface_offset:.3f} "
f"eye_z_m={camera[2]:.3f} "
f"camera_location={view.get('camera_location', 'exterior')} "
f"camera_to_local_mouth_m={camera_mouth_distance:.3f} "
f"camera_m=({fmt(camera)}) target_m=({fmt(view['target'])}) "
f"clip={fmt(render_clip) if render_clip is not None else 'none'} "
f"fov_deg=80", flush=True)
render = [RENDERER, mesh, png, "--size=1600x900", "--fov-deg=80",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}",
"--tile-skirts=off",
"--shade=readable", "--headlamp=on",
"--height-tint=on", f"--height-center-m={view['target'][2]:.6f}",
"--height-range-m=8", "--tile-edges=off"]
if view.get("focus_radius", 24.0) is not None:
render.append(f"--focus-region={fmt((*view['focus'], view.get('focus_radius', 24.0)))}")
if render_clip is not None:
render.append(f"--clip-plane={fmt(render_clip)}")
run(f"{stem}-render", render)
run(f"{stem}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error",
"-y", "-i", png, "-frames:v", "1", "-q:v", "4", jpg])
size = jpg.stat().st_size if jpg.exists() else 0
print(f"[L37CaptureJPEG] file={jpg.relative_to(ROOT)} bytes={size} "
f"status={'PASS' if 0 < size <= 300_000 else 'FAIL'}", flush=True)
if not 0 < size <= 300_000:
raise SystemExit(f"{jpg.name} is missing or exceeds 300 KB")
@@ -0,0 +1,58 @@
# L37 report
## Result
The owner accepted the FI crater mouth as captured, so I kept its shape. The raised crossing came from the FI surface route being lifted above the crater by its capsule-footing/grade envelope; it was not the short-span bridge deck. Moving the route onto the crater rim and making the style-0 path follow the native surface removed the raised crossing while keeping the walk route supported. Passage, capsule, support and open-air audits pass from arrival to departure.
The signed carve-fade fix remains: the composed field blends toward the full signed carve distance by the fade weight. The unfinished 0.50 m relief-wavelength trial was rejected; the prior 0.75 m wavelength remains.
## Visual audits
The built-shape thresholds were fixed from player scale: straight run 0.75 m (just above the 0.68 m capsule width), planar patch 0.50 m², corner turn 30°, and plane tolerance 0.02 m. The planar metrics remain in the output, but the owner accepted this FI appearance and the planar gate is informational for this round.
- FI: added planar patch 2.0098 m², added straight run 1.0000 m, zero sharp corners. The capture has no straight raised bridge edge, but the straight-run proxy is over its 0.75 m gate; the shape audit therefore still flags FI on that metric.
- SW: no changed surface area in the matched route corridor; passes.
- Upper-SW fixture: added planar patch 0.3319 m², straight run 0.7500 m, zero corners; passes (`0.75 m` is not longer than the limit).
- Negative controls: L36 FI fails (4.8476 m², 1.4142 m); the 3×2 m box fails (6 m², 3 m, four corners). Both are detected.
The crease gate was frozen at 20° before the fix. L35 (42.27°) and L36 (27.13°) fail as controls. L37 is 4.40° at the 6.016 m close camera. In the 17.001 m overview it reaches 25.54° across 0.125 m, a 14.34 px projected interval at 2560×1440, so the numerical overview gate fails. A temporary v208 support-off build reproduces the same pair; the tread/support is not its source. The extra overview shows no continuous bridge seam, and I left the owner-approved crater unchanged. The FI fragment check finds zero detached components.
## Verification
- Passage audits: 24/24 pass across FI styles 0–3, SW styles 0–3, and upper-SW fixture styles 0–3, with Clang and GCC. Recipe stamps, mouth profiles, wall/floor ripple, strict validation, walkability, support, capsule, open air, and route bindings pass.
- Invariants: 16/16. Legacy L13/L9: 4/4. Interface/hole and ring audits: 18/18. L30 terrace and L31 flat controls returned the expected nonzero result and were detected.
- Determinism: 30/30, including L25 at W1/W8/W12 with Clang/GCC for both recipes, the 24 m crossing, and the upper-SW fixture.
- Cost: five interleaved pairs per recipe against L36. Median whole-view change: +0.63% SW and +2.04% FI; paired MAD was 1.93% and 5.71%, respectively. No regression beyond run noise.
- Captures use the standard L34 cameras, plus the 17.001 m FI overview. All 13 JPEGs are under 300 KB in `captures/`.
## Version and hashes
Evaluator version is 208 (bumped from 207). Stable hashes: recipe `c74a93d3e274e217`; owner plan `a5e29887e839c5d6`; owner manifest `8482dd0287ee2e9f`; FI field recipe `2096bd6fcf4d498d`. Upper-SW fixture layout ID is `resolved-layout-v3-fnv64:fca86f7273c53410` (plan `463a0f3b72909656`, manifest `98110083aff9ef5d`).
Final binary SHA-256: Clang `d69a1e0dc3275b4fa63963326c44588586fb7adc7db7a85e9c773ba9d9aa1e7d`; GCC `d75d282c5683584803a9b4adb2655aa0e66192de648f20215ec8702363b11c88`. FI mesh SHA-256: `280d238f3111f8a26348591600853fe89fe72c1ec1b250ce9d00ec7185543115`.
## Source edits
Core changes are in `VoxelForgeCore.h`, `FloatingIslandsInternal.h`, `ResolvedLayoutInternal.h`, `ShapeWorldInternal.h`, and `WorldAssemblyPrototype.h`. The only Unreal edit is the `terrainFollowingRoute` recipe setter in `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`; it could not be compiled on this Linux host. No invariants were changed.
## Failed and retried runs
- v140–153: deck-off was byte-identical but failed rock columns; v141/143 offsets left 25 unsupported samples; v142 made an isolated island; v144 left shards; v145 made holes/slots (12 support, 2 open-air failures); v146 left the bridge and 15 support failures; v147 grade rejected; v148 detached spiral blocked 43 samples; v149 removed bridge but blocked 48 and missed 3 supports; v150 pillar and v151 horn each missed 3 supports; v152 had no effect and was reverted; v153 was interrupted, retried, then its 1 m sink failed ripple/support.
- v154–180: route/ripple/support failures continued; v157 floor ripple was 3.09 cm; v158 exposed an arch; v159 passed capsule but failed ripple; v160 missed support with bridge visible; v161 taper failed ripple; v162/163 disabled blend and left 8/16 unsupported; v164 left a fin; v165/167/168 left the bridge; v166 had a compile error, was corrected/rebuilt, then had 18 unsupported samples; v169 sink removed horns but left 4 misses; v170/171 gates were inert; v172 left an arch; v173 removed the fin but missed 4 supports; v174 recovered support but retained a fin; v175 changed nothing; v176 failed wall/floor ripple and capsule; v177 blocked 103 and failed ripple; v178–180 sink variants left a fin or support misses. The v180 build was interrupted twice (exit 130) before the successful retry.
- v181–188: v181 route sag left 2 unsupported; v182 connector removal exited 2 on capsule-width support; v183 rounded joins passed route checks but ripple was 1.91 cm; v184 first compile failed on a qualifier, then rebuilt but ripple still failed; v185 removed the bridge but ripple stayed 1.91 cm; v186 support smoothing had no ripple gain; v187 wall-relief trial had no effect and was reverted; v188 first compile failed on `RoundQ`, fixed with `std::llround`; v188 then passed style-0 route/capsule/open-air and reduced floor ripple to 1.39 cm.
- v189–201: v189–192 had open-air, style-2 blocked-sample, or ripple failures; v193 fixed style-2 with a 1 m underfloor/fade but style-0 ripple was 1.56 cm; v194 native blending did not help the other styles; v195 passed style 0 but not styles 1–3; v196’s style-gated curve caused 175/186/136 open-air misses; v197’s 0.50 m relief wavelength failed and was restored to 0.75 m; v198 amplitude reduction had no benefit and was reverted; v199 surface-height sampling left style failures; v200’s 2 m arc had 1.80 cm floor ripple; v201’s 2.5 m arc passed style 0 but not styles 1–3.
- v202–207: style-0-only route/filter/tread variants still failed other styles: v202 had 175/186/136 open-air misses; v203 restricted tread and worsened open-air; v204’s 2 m nonzero-style curve had 92 misses; v205 had 77/94/36 misses; v206 fixed open air but styles 2/3 failed ripple; v207 fixed style 2 but style 1 floor ripple was 2.00 cm and style 3 was 3.19 cm. v208 passed the full passage matrix.
- The mistaken `RunPassageAudits --help` invocation ran a case and timed out at 30 s (exit 124). A PIL probe failed with `ModuleNotFoundError`; nothing was installed. The final shape audit still flags FI’s 1.00 m straight-run proxy, and the final crease audit still flags the 17 m overview at 20°. The temporary support-off diagnostic reproduced that crease; its source edit was restored before final verification. Ring negative-control binaries exited 1 as expected and were detected.
## Orchestrator review (Claude)
- Accepted, with the owner's verdict on the crater look. The carve-fade bug fix is real and general. The raised
bridge is gone in the style-0 capture, and the full matrix, determinism and controls pass.
- **Captures since L34 labelled "owner-fi"/"owner-sw" use the style-0 override fixtures.** The owner world itself
uses **Style 2 (Spiral) on FI and Style 1 on SW**. So the owner judged a style he doesn't currently use, and the
route change here (`terrainFollowingRoute = style == 0`) does **not** apply to his real FI passage. My brief said
"owner world"; I didn't catch the fixture choice until now. L38 captures the real styles.
- The fix is **style-gated** (many `style==0 && anchoredIslandPassage && !anchoredSpiral` branches). That breaks the
shared-layer rule. L38 replaces it with one general mechanism (Research1 proposal 1: floor-limited carve).
- The evaluator version jumped 23 → 208 (one bump per trial) and left `>= 192`-style gates in the code. Harmless for
determinism, but it's clutter; L38 should collapse the trial-only gates.
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""Run five interleaved whole-view cost pairs against the L36 Clang binary."""
import csv
import math
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l37"
BINARIES = {
"l36": ROOT / "Saved/Linux/l36/bin/VoxelForgeCoreHeadless-l36-final-clang",
"l37": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
}
ORDERS = (("l36", "l37"), ("l37", "l36"), ("l36", "l37"),
("l37", "l36"), ("l36", "l37"))
READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
STAMP = re.compile(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=PASS$", re.M)
rows = []
failed = False
for recipe in ("surface-world", "floating-islands"):
for pair, order in enumerate(ORDERS, 1):
for build in order:
name = f"cost-{recipe}-pair{pair}-{build}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s", str(BINARIES[build]),
str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = logfile.read_text(encoding="utf-8", errors="replace")
matches = READY.findall(output)
stamps = STAMP.findall(output)
stamp_ok = stamps == [(recipe, recipe)]
valid = (result is not None and result.returncode == 0 and len(matches) == 1
and "hash=off" in output and "cache_hits=0" in output and stamp_ok)
ready = float(matches[0]) if len(matches) == 1 else math.nan
code = result.returncode if result is not None else "TIMEOUT"
rows.append((recipe, pair, build, ready, elapsed, code, valid))
failed |= not valid
print(f"[L37CostRun] recipe={recipe} pair={pair} build={build} "
f"whole_view_ready={ready:.3f}s process_elapsed={elapsed:.1f}s "
f"{'PASS' if valid else 'FAIL'} log={logfile}", flush=True)
summary = OUT / "cost-l37.csv"
with summary.open("w", newline="", encoding="utf-8") as stream:
writer = csv.writer(stream)
writer.writerow(("recipe", "pair", "build", "whole_view_ready_seconds",
"process_elapsed_seconds", "exit_code", "status"))
writer.writerows((recipe, pair, build, f"{ready:.6f}", f"{elapsed:.3f}", code,
"PASS" if valid else "FAIL")
for recipe, pair, build, ready, elapsed, code, valid in rows)
for recipe in ("surface-world", "floating-islands"):
by_build = {build: [row[3] for row in rows if row[0] == recipe and row[2] == build]
for build in BINARIES}
baseline = statistics.median(by_build["l36"])
current = statistics.median(by_build["l37"])
paired = []
for pair in range(1, 6):
values = {row[2]: row[3] for row in rows if row[0] == recipe and row[1] == pair}
paired.append((values["l37"] - values["l36"]) / values["l36"] * 100.0)
mad = statistics.median(abs(value - statistics.median(paired)) for value in paired)
print(f"[L37CostSummary] recipe={recipe} pairs=5 median_l36_s={baseline:.3f} "
f"median_l37_s={current:.3f} median_change_pct={(current/baseline-1)*100.0:+.2f} "
f"paired_change_pct={','.join(f'{value:+.2f}' for value in paired)} "
f"paired_mad_pct={mad:.2f}", flush=True)
print(f"[L37Cost] runs={len(rows)} status={'FAIL' if failed else 'PASS'} summary={summary}",
flush=True)
raise SystemExit(1 if failed or len(rows) != 20 else 0)
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Check L25, 24 m crossing and upper-SW fixture determinism for L37."""
import csv
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l37"
OWNER = ROOT / "Standalone/data/owner_world.json"
FIXTURE = ROOT / "Saved/Linux/l33/fixtures/sw-upper-tunnel-network-style0.json"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l37-final-gcc",
}
HASH = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
STAMP = re.compile(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$", re.M)
LAYOUT = re.compile(
r"^\[ResolvedLayoutSnapshot\] schema=\d+ stable_identity=([^ ]+) strata=\d+ bytes=\d+ round_trip=PASS$",
re.M)
PLAN = re.compile(
r"^\[WorldAssemblerPlanSerialization\].*canonical_round_trip=true .*"
r"plan_hash=([0-9a-f]+) manifest_hash=([0-9a-f]+) status=PASS$", re.M)
ROUTE = re.compile(
r"^\[WorldAssemblerRouteAccepted\] arrival_xy_m=\([^)]*\) "
r"departure_xy_m=\([^)]*\) endpoint_proximity_cost=\d+ average_grade_q16=\d+$", re.M)
rows = []
failed = False
references = {}
def run(kind, recipe, compiler, workers, data, passage_audit=False, crossing=False):
global failed
binary = BINARIES[compiler]
name = f"determinism-{kind}-{compiler}-{recipe}-w{workers}"
if kind == "upper-fixture":
name = f"determinism-upper-fixture-{compiler}-w{workers}"
log = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", str(binary), str(data), f"--workers={workers}",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}",
]
if passage_audit:
command.append("--audit-resolved-layout-passages")
else:
command.append("--stream-hash")
if crossing:
command.extend(("--camera-step=24,0,0", "--region-crossing"))
started = time.monotonic()
try:
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = log.read_text(encoding="utf-8", errors="replace")
stamps = STAMP.findall(output)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
if passage_audit:
signature = (tuple(LAYOUT.findall(output)), tuple(PLAN.findall(output)),
tuple(ROUTE.findall(output)))
expected = references.get((kind, recipe))
stable = expected is None or signature == expected
if expected is None and all(signature):
references[(kind, recipe)] = signature
valid = (result is not None and result.returncode == 0 and stamp_ok
and len(signature[0]) == len(signature[1]) == len(signature[2]) == 1
and re.search(r"^\[ResolvedPassageAuditSummary\].*status=PASS$", output, re.M)
and re.search(r"^\[PassageRippleAuditSummary\].*status=PASS$", output, re.M)
and stable)
hashes = ()
detail = f"layout={signature[0][0] if signature[0] else 'MISSING'} stable={stable}"
else:
hashes = tuple(HASH.findall(output))
key = (kind, recipe)
expected = references.get(key)
stable = expected is None or hashes == expected
if expected is None and hashes:
references[key] = hashes
valid = (result is not None and result.returncode == 0 and stamp_ok
and "hash=on" in output and len(hashes) == 12 and stable)
detail = f"hashes={len(hashes)}/12 stable={stable}"
code = result.returncode if result is not None else "TIMEOUT"
rows.append((kind, recipe, compiler, workers, elapsed, code, valid))
failed |= not valid
print(f"[L37DeterminismRun] kind={kind} recipe={recipe} compiler={compiler} "
f"workers={workers} elapsed={elapsed:.1f}s {detail} "
f"{'PASS' if valid else 'FAIL'} log={log}", flush=True)
for kind in ("l25", "crossing24"):
for recipe in ("surface-world", "floating-islands"):
for compiler in BINARIES:
for workers in (1, 8, 12):
run(kind, recipe, compiler, workers, OWNER,
crossing=(kind == "crossing24"))
for compiler in BINARIES:
for workers in (1, 8, 12):
run("upper-fixture", "floating-islands", compiler, workers, FIXTURE,
passage_audit=True)
summary = OUT / "determinism-l37.csv"
with summary.open("w", newline="", encoding="utf-8") as stream:
writer = csv.writer(stream)
writer.writerow(("kind", "recipe", "compiler", "workers", "elapsed_seconds",
"exit_code", "status"))
for row in rows:
writer.writerow((*row[:-1], "PASS" if row[-1] else "FAIL"))
print(f"[L37DeterminismSummary] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 30 else 0)
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Run L33 route/proof and transition invariants with recipe-stamped logs."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l37"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l37-final-gcc",
}
rows = []
failed = False
def run(name, binary, data, recipe, extra, checks):
global failed
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s",
str(binary), str(data), "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}", *extra,
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
stamps = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
text, re.M)
stamp_ok = stamps == [(recipe, recipe, "PASS")]
checks_ok = all(check(text) for check in checks)
ok = result is not None and result.returncode == 0 and stamp_ok and checks_ok
rows.append((name, recipe, elapsed, result.returncode if result else "TIMEOUT", ok))
print(f"{name} elapsed={elapsed:.1f}s recipe={stamps[0][1] if stamps else 'MISSING'} "
f"stamp={'PASS' if stamp_ok else 'FAIL'} checks={'PASS' if checks_ok else 'FAIL'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
failed |= not ok
owner = ROOT / "Standalone/data/owner_world.json"
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
name = f"invariant-route-proof-{recipe}-{compiler}"
run(name, binary, owner, recipe,
["--stream-hash", "--audit-uniform-block-proofs", "--audit-route-parity"],
[lambda text: bool(re.search(
r"^\[RouteParityAudit\].*status=PASS$", text, re.M)),
lambda text: bool(re.search(
r"^\[UniformBlockProofAudit\].*violations=0(?:\s|$)", text, re.M))])
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
for mode in ("hard", "gradient", "interleaved"):
name = f"invariant-transition-{recipe}-{mode}-{compiler}"
run(name, binary, owner, recipe,
[f"--transition-mode={mode}", "--audit-strate-transitions"],
[lambda text: bool(re.search(
r"^\[StrateTransitionAuditSummary\].*hole_columns=0 air_samples=0 .*status=PASS$",
text, re.M)),
lambda text: bool(re.search(
r"^\[StrateTransitionNegativeControls\].*status=PASS$", text, re.M))])
summary = OUT / "invariant-audits.csv"
summary.write_text(
"run,recipe,elapsed_seconds,exit_code,status\n" +
"".join(f"{name},{recipe},{elapsed:.3f},{code},{'PASS' if ok else 'FAIL'}\n"
for name, recipe, elapsed, code, ok in rows),
encoding="utf-8")
print(f"[L33InvariantAudits] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 16 else 0)
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Run the L13 route parity and L9 uniform-block proof audits on L37."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l37"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l37-final-gcc",
}
RECIPES = ("floating-islands", "surface-world")
rows = []
failed = False
for recipe in RECIPES:
for compiler, binary in BINARIES.items():
name = f"legacy-route-proof-{recipe}-{compiler}"
logfile = OUT / f"{name}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s", str(binary), str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--stream-hash", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}", "--audit-uniform-block-proofs", "--audit-route-parity",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=900, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = logfile.read_text(encoding="utf-8", errors="replace")
stamp = re.findall(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
output, re.M)
route = re.findall(r"^\[RouteParityAudit\].*$", output, re.M)
proofs = re.findall(r"^\[UniformBlockProofAudit\].*$", output, re.M)
stamp_ok = stamp == [(recipe, recipe, "PASS")]
route_ok = len(route) == 1 and route[0].endswith("status=PASS")
proof_ok = len(proofs) == 1 and bool(
re.search(r"violations=0(?:\s|$)", proofs[0]))
ok = (result is not None and result.returncode == 0 and stamp_ok
and route_ok and proof_ok)
code = result.returncode if result is not None else "TIMEOUT"
rows.append((name, recipe, compiler, elapsed, code, route_ok, bool(proof_ok), ok))
failed |= not ok
print(f"[L37LegacyAuditRun] case={name} elapsed={elapsed:.1f}s exit={code} "
f"recipe_stamp={'PASS' if stamp_ok else 'FAIL'} "
f"L13={'PASS' if route_ok else 'FAIL'} L9={'PASS' if proof_ok else 'FAIL'} "
f"{'PASS' if ok else 'FAIL'}", flush=True)
summary = OUT / "legacy-audits.csv"
summary.write_text(
"case,recipe,compiler,elapsed_seconds,exit_code,l13,l9,status\n" +
"".join(f"{name},{recipe},{compiler},{elapsed:.3f},{code},"
f"{'PASS' if l13 else 'FAIL'},{'PASS' if l9 else 'FAIL'},"
f"{'PASS' if ok else 'FAIL'}\n"
for name, recipe, compiler, elapsed, code, l13, l9, ok in rows),
encoding="utf-8")
print(f"[L37LegacyAuditSummary] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 4 else 0)
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""Freeze a 20-degree mouth-crease gate and project its sample spacing at 1440p."""
import ast
import contextlib
import importlib.util
import io
import math
import re
import argparse
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
BASE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37/RunMouthFragmentAuditL37.py"
SPEC = importlib.util.spec_from_file_location("mouth_fragment_l37", BASE_PATH)
BASE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(BASE)
# Frozen before the L37 mouth edit: 20 degrees over the audit's 0.125 m samples is
# the visible-crease limit. At the 6.016 m standard camera and 80-degree horizontal
# FOV, each sample gap spans multiple pixels at 2560x1440.
BASE.NORMAL_LIMIT_DEG = 20.0
CAMERA = (1.691820, -3.424982, -54.224198)
TARGET = (6.298233, 1.137233, -55.468264)
FOV_DEG = 80.0
WIDTH_1440P = 2560
HEIGHT_1440P = 1440
MOUTH = (5.600, 0.445, -55.156)
OVERVIEW_CAMERA = (MOUTH[0] - 9.75, MOUTH[1] - 11.4, MOUTH[2] + 8.0)
def normalize(vector):
magnitude = math.sqrt(sum(value * value for value in vector))
return tuple(value / magnitude for value in vector)
def view_basis(camera, target):
forward = normalize(tuple(target[i] - camera[i] for i in range(3)))
right = normalize((forward[1], -forward[0], 0.0))
up = (right[1] * forward[2] - right[2] * forward[1],
right[2] * forward[0] - right[0] * forward[2],
right[0] * forward[1] - right[1] * forward[0])
return forward, right, up
def visible_crease(path, camera, target):
cx, cy = BASE.CENTER
bounds = (cx - BASE.OUTER, cx + BASE.OUTER, cy - BASE.OUTER, cy + BASE.OUTER)
voxel_um, triangles, _ = BASE.RING.read_scene(
str(path), 0, BASE.HEIGHT_MIN, BASE.HEIGHT_MAX, bounds)
cell = max(2.0, 16.0 * voxel_um / 1_000_000.0)
buckets = BASE.RING.build_buckets(triangles, True, bounds, cell)
forward, right, up = view_basis(camera, target)
tan_h = math.tan(math.radians(FOV_DEG / 2.0))
tan_v = math.tan(math.atan(tan_h * HEIGHT_1440P / WIDTH_1440P))
focal = WIDTH_1440P / (2.0 * tan_h)
lines = []
count = round((BASE.OUTER - BASE.INNER) / BASE.STEP)
for degrees in (135.0, 180.0, 225.0, 270.0, 315.0):
angle = math.radians(degrees)
lines.append([(cx + radius * math.cos(angle), cy + radius * math.sin(angle))
for radius in (BASE.INNER + index * BASE.STEP
for index in range(count + 1))])
tangent_count = round(8.0 / BASE.STEP)
for degrees in (225.0, 315.0):
angle = math.radians(degrees)
normal = (math.cos(angle), math.sin(angle))
tangent = (-normal[1], normal[0])
lines.append([(cx + 6.5 * normal[0] + (-4.0 + index * BASE.STEP) * tangent[0],
cy + 6.5 * normal[1] + (-4.0 + index * BASE.STEP) * tangent[1])
for index in range(tangent_count + 1)])
maximum, largest_pixels, worst_depth, visible_pairs = 0.0, 0.0, 0.0, 0
worst_pair_pixels = 0.0
worst_pair = None
def project(point):
offset = tuple(point[i] - camera[i] for i in range(3))
depth = sum(offset[i] * forward[i] for i in range(3))
x = sum(offset[i] * right[i] for i in range(3))
y = sum(offset[i] * up[i] for i in range(3))
return depth, x, y
for points in lines:
samples = [BASE.RING.sample_surface(buckets, x, y, True, cell, BASE.PREFERRED)
for x, y in points]
for index in range(1, len(samples)):
before, current = samples[index - 1], samples[index]
if before is None or current is None:
continue
p0 = (points[index - 1][0], points[index - 1][1], before[0])
p1 = (points[index][0], points[index][1], current[0])
midpoint = tuple((p0[axis] + p1[axis]) * 0.5 for axis in range(3))
depth, horizontal, vertical = project(midpoint)
if (depth <= 0.0 or abs(horizontal) > depth * tan_h
or abs(vertical) > depth * tan_v):
continue
visible_pairs += 1
angle = math.degrees(math.acos(max(-1.0, min(1.0,
sum(a * b for a, b in zip(before[1], current[1]))))))
d0, x0, y0 = project(p0)
d1, x1, y1 = project(p1)
pixels = math.hypot(focal * x0 / d0 - focal * x1 / d1,
focal * y0 / d0 - focal * y1 / d1)
largest_pixels = max(largest_pixels, pixels)
if angle > maximum:
maximum, worst_pair, worst_depth = angle, (p0, p1), depth
worst_pair_pixels = pixels
return maximum, worst_depth, largest_pixels, visible_pairs, worst_pair, worst_pair_pixels
def run(path, label):
stream = io.StringIO()
with contextlib.redirect_stdout(stream):
passed, maximum = BASE.audit(path)
output = stream.getvalue()
print(output, end="")
match = re.search(r"worst_pair=(\([^\n]*\)) status=", output)
worst = ast.literal_eval(match.group(1)) if match else None
path = Path(path)
near = visible_crease(path, CAMERA, TARGET)
far = visible_crease(path, OVERVIEW_CAMERA, TARGET)
print(f"[MouthCreasePixels] build={label} sample_step_m={BASE.STEP:.3f} "
f"horizontal_fov_deg={FOV_DEG:.1f} resolution=2560x1440 "
f"near_camera_to_mouth_m=6.016 near_visible_pairs={near[3]} "
f"near_max_visible_deg={near[0]:.6f} near_depth_m={near[1]:.3f} "
f"near_sample_spacing_px={near[2]:.2f} "
f"near_worst_pair_spacing_px={near[5]:.2f} "
f"overview_camera_to_mouth_m={math.dist(OVERVIEW_CAMERA, MOUTH):.3f} "
f"overview_visible_pairs={far[3]} overview_max_visible_deg={far[0]:.6f} "
f"overview_depth_m={far[1]:.3f} overview_sample_spacing_px={far[2]:.2f} "
f"overview_worst_pair_spacing_px={far[5]:.2f} "
f"overall_max_deg={maximum:.6f}")
return passed, maximum
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--candidate", type=Path,
default=ROOT / "Saved/Linux/l37/owner-fi-ground-mouth.vfmesh")
args = parser.parse_args()
paths = {
"L35": ROOT / "Saved/Linux/l35/owner-fi-ground-mouth.vfmesh",
"L36": ROOT / "Saved/Linux/l36/owner-fi-ground-mouth.vfmesh",
"L37": args.candidate if args.candidate.is_absolute()
else ROOT / args.candidate,
}
results = {label: run(path, label) for label, path in paths.items()}
controls = (not results["L35"][0] and not results["L36"][0]
and results["L35"][1] > 20.0 and results["L36"][1] > 20.0)
final = results["L37"][0] and results["L37"][1] <= 20.0
print(f"[MouthCreaseGate] normal_limit_deg=20 frozen_before_fix=true "
f"L35_42deg_fail_control={'PASS' if controls else 'FAIL'} "
f"L36_27deg_fail_control={'PASS' if controls else 'FAIL'} "
f"L37_candidate={'PASS' if final else 'FAIL'} "
f"status={'PASS' if controls and final else 'FAIL'}")
raise SystemExit(0 if controls and final else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,176 @@
#!/usr/bin/env python3
"""Compare the FI fade-ring crease against L35 using the same surface samples."""
import argparse
import importlib.util
import math
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RING_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
SPEC = importlib.util.spec_from_file_location("ring_profile_l32", RING_PATH)
RING = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(RING)
CENTER = (6.298233, 1.137233)
INNER, OUTER, STEP = 5.5, 9.5, 0.125
HEIGHT_MIN, HEIGHT_MAX, PREFERRED = -80.0, -40.0, -56.0
# Chosen before the L37 mouth edit: a 20-degree normal jump over one 0.125 m
# sample interval is visible at the standard camera distance.
NORMAL_LIMIT_DEG = 20.0
COVERAGE_MIN = 0.90
FRAGMENT_MAX_TRIANGLES = 64
FRAGMENT_MIN_AREA_M2 = 0.0001
FRAGMENT_MAX_AREA_M2 = 0.05
FRAGMENT_MAX_DISTANCE_M = 12.0
def small_components(triangles):
parent = list(range(len(triangles)))
sizes = [1] * len(triangles)
owners = {}
def find(index):
while parent[index] != index:
parent[index] = parent[parent[index]]
index = parent[index]
return index
def union(a, b):
a, b = find(a), find(b)
if a == b:
return
if sizes[a] < sizes[b]:
a, b = b, a
parent[b] = a
sizes[a] += sizes[b]
for index, (_, _, vertices) in enumerate(triangles):
for point, _ in vertices:
key = tuple(round(value, 5) for value in point)
other = owners.setdefault(key, index)
union(index, other)
components = {}
for index, (_, _, vertices) in enumerate(triangles):
root = find(index)
item = components.setdefault(root, {"count": 0, "area": 0.0, "sum": [0.0] * 3})
item["count"] += 1
points = [vertex[0] for vertex in vertices]
for point in points:
for axis in range(3):
item["sum"][axis] += point[axis]
ab = tuple(points[1][axis] - points[0][axis] for axis in range(3))
ac = tuple(points[2][axis] - points[0][axis] for axis in range(3))
cross = (ab[1] * ac[2] - ab[2] * ac[1],
ab[2] * ac[0] - ab[0] * ac[2],
ab[0] * ac[1] - ab[1] * ac[0])
item["area"] += 0.5 * math.sqrt(sum(value * value for value in cross))
cx, cy = CENTER
small = []
below_floor = []
for item in components.values():
centroid = tuple(value / (3 * item["count"]) for value in item["sum"])
distance = math.hypot(centroid[0] - cx, centroid[1] - cy)
if (item["count"] <= FRAGMENT_MAX_TRIANGLES
and item["area"] <= FRAGMENT_MAX_AREA_M2
and distance <= FRAGMENT_MAX_DISTANCE_M):
record = (item["count"], item["area"], distance, centroid)
(below_floor if item["area"] < FRAGMENT_MIN_AREA_M2 else small).append(record)
return len(components), small, below_floor
def audit(path):
cx, cy = CENTER
bounds = (cx - OUTER, cx + OUTER, cy - OUTER, cy + OUTER)
voxel_um, triangles, tiles = RING.read_scene(
str(path), 0, HEIGHT_MIN, HEIGHT_MAX, bounds)
component_count, fragments, below_floor = small_components(triangles)
print(f"[MouthFragmentComponents] mesh={path.name} components={component_count} "
f"small_near_mouth={len(fragments)} subthreshold_near_mouth={len(below_floor)} "
f"limits=(triangles<={FRAGMENT_MAX_TRIANGLES},"
f"area={FRAGMENT_MIN_AREA_M2:.4f}..{FRAGMENT_MAX_AREA_M2:.3f}m2,"
f"distance<={FRAGMENT_MAX_DISTANCE_M:.1f}m) "
f"first={fragments[0] if fragments else None} "
f"subthreshold={below_floor[0] if below_floor else None} "
f"status={'PASS' if not fragments else 'FAIL'}")
cell = max(2.0, 16.0 * voxel_um / 1_000_000.0)
buckets = RING.build_buckets(triangles, True, bounds, cell)
lines = []
count = round((OUTER - INNER) / STEP)
for degrees in (135.0, 180.0, 225.0, 270.0, 315.0):
angle = math.radians(degrees)
lines.append((f"radial-{degrees:.0f}", [
(cx + radius * math.cos(angle), cy + radius * math.sin(angle))
for radius in (INNER + i * STEP for i in range(count + 1))]))
tangent_count = round(8.0 / STEP)
for degrees in (225.0, 315.0):
angle = math.radians(degrees)
normal = (math.cos(angle), math.sin(angle))
tangent = (-normal[1], normal[0])
lines.append((f"tangent-{degrees:.0f}", [
(cx + 6.5 * normal[0] + (-4.0 + i * STEP) * tangent[0],
cy + 6.5 * normal[1] + (-4.0 + i * STEP) * tangent[1])
for i in range(tangent_count + 1)]))
max_angle = 0.0
max_pair = None
min_coverage = 1.0
for name, points in lines:
samples = [RING.sample_surface(buckets, x, y, True, cell, PREFERRED)
for x, y in points]
coverage = sum(value is not None for value in samples) / len(samples)
min_coverage = min(min_coverage, coverage)
line_max = 0.0
for index in range(1, len(samples)):
prior, current = samples[index - 1], samples[index]
if prior is None or current is None:
continue
dot = max(-1.0, min(1.0, sum(a * b for a, b in zip(prior[1], current[1]))))
delta = math.degrees(math.acos(dot))
if delta > line_max:
line_max = delta
if delta > max_angle:
max_angle = delta
max_pair = (name, points[index - 1], prior[0], points[index], current[0])
print(f"[MouthFadeLine] mesh={path.name} line={name} coverage={coverage:.3f} "
f"max_normal_jump_deg={line_max:.6f}")
normal_pass = max_angle <= NORMAL_LIMIT_DEG and min_coverage >= COVERAGE_MIN
passed = normal_pass and not fragments
print(f"[MouthFadeAudit] mesh={path.name} tiles={tiles} lines={len(lines)} "
f"coverage_min={min_coverage:.3f} coverage_limit={COVERAGE_MIN:.2f} "
f"max_normal_jump_deg={max_angle:.6f} normal_limit_deg={NORMAL_LIMIT_DEG:.1f} "
f"fragments={len(fragments)} "
f"worst_pair={max_pair} status={'PASS' if passed else 'FAIL'}")
return passed, max_angle
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--control", type=Path,
default=ROOT / "Saved/Linux/l35/owner-fi-ground-mouth.vfmesh")
parser.add_argument("--prior", type=Path,
default=ROOT / "Saved/Linux/l36/owner-fi-ground-mouth.vfmesh")
parser.add_argument("--current", type=Path,
default=ROOT / "Saved/Linux/l37/owner-fi-ground-mouth.vfmesh")
args = parser.parse_args()
control = args.control if args.control.is_absolute() else ROOT / args.control
prior = args.prior if args.prior.is_absolute() else ROOT / args.prior
current = args.current if args.current.is_absolute() else ROOT / args.current
control_ok, control_max = audit(control)
prior_ok, prior_max = audit(prior)
current_ok, current_max = audit(current)
controls = (not control_ok and not prior_ok
and control_max > NORMAL_LIMIT_DEG and prior_max > NORMAL_LIMIT_DEG)
expected = controls and current_ok and current_max <= NORMAL_LIMIT_DEG
print(f"[MouthFadeNegativeControl] L35_max_deg={control_max:.6f} "
f"L35_detected={not control_ok} L36_max_deg={prior_max:.6f} "
f"L36_detected={not prior_ok} L37_max_deg={current_max:.6f} "
f"normal_limit_deg={NORMAL_LIMIT_DEG:.1f} current_pass={current_ok} "
f"status={'PASS' if expected else 'FAIL'}")
raise SystemExit(0 if expected else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Run recipe-stamped passage/profile audits for both compilers and both owner recipes."""
import re
import os
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l37"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
"gcc": OUT / "bin/VoxelForgeCoreHeadless-l37-final-gcc",
}
RUNS = []
for recipe, filename in (
("floating-islands", "floating-islands"),
("surface-world", "surface-world"),
):
for style in range(4):
RUNS.append((f"owner-{filename}-style{style}",
ROOT / f"Saved/Linux/linux25-owner-{filename}-style{style}.json", recipe))
for style in range(4):
RUNS.append((f"upper-sw-fixture-style{style}",
OUT.parent / "l33/fixtures" / f"sw-upper-tunnel-network-style{style}.json",
"floating-islands"))
RECIPE = re.compile(r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=PASS$", re.M)
PASSAGE = re.compile(r"^\[ResolvedPassageAudit\].*walkability=PASS$", re.M)
MOUTH = re.compile(r"^\[PassageMouthProfileAudit\].*status=PASS$", re.M)
ROUTE = re.compile(r"^\[ResolvedStrateRouteAudit\].*mouth_binding=PASS status=PASS$", re.M)
CHECKS = (
r"^\[ResolvedLayoutStrict\] accepted=true issues=0 error=$",
r"^\[ResolvedLayoutValidatorControls\].*status=PASS$",
r"^\[PassageMouthProfileNegativeControl\].*source=L23_terraced_geometry.*detected=true$",
r"^\[PassageMouthProfileSummary\].*l23_terrace_negative_control=DETECTED status=PASS$",
r"^\[PassageNaturalnessSummary\].*l23_negative_control=DETECTED.*status=PASS$",
r"^\[PassageRippleAuditSummary\].*synthetic_spheres_negative_control=DETECTED max_amplitude_m=0\.035 floor_beaded_negative_control=DETECTED max_floor_amplitude_m=0\.015 status=PASS$",
r"^\[PassageOpenAirAuditSummary\].*violations=0.*l24_negative_control=DETECTED status=PASS$",
r"^\[PassageNoLongBridgeAudit\].*status=PASS$",
r"^\[PassageRockColumnAuditSummary\].*status=PASS$",
r"^\[ResolvedPassageAuditSummary\].*route_binding_failures=0 .*status=PASS$",
r"^\[Walkability\] PASS",
r"^\[FloorProfileBlend\].*negative_controls=step_detected/slope_detected status=PASS$",
)
rows = []
failed = False
for compiler, binary in BINARIES.items():
for name, data, recipe in RUNS:
logfile = OUT / f"passage-audit-{name}-{compiler}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", str(binary), str(data), "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
f"--recipe={recipe}", "--audit-resolved-layout-passages",
]
started = time.monotonic()
try:
with logfile.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = logfile.read_text(encoding="utf-8", errors="replace")
stamp_ok = RECIPE.findall(output) == [(recipe, recipe)]
passages = len(PASSAGE.findall(output))
mouths = len(MOUTH.findall(output))
routes = len(ROUTE.findall(output))
checks_ok = all(re.search(pattern, output, re.M) for pattern in CHECKS)
ok = (result is not None and result.returncode == 0 and stamp_ok
and passages >= 1 and mouths == passages and routes >= 1 and checks_ok)
code = result.returncode if result is not None else "TIMEOUT"
rows.append((name, recipe, compiler, elapsed, code, passages, mouths, routes, ok))
failed |= not ok
print(f"[L37PassageAuditRun] case={name} compiler={compiler} recipe={recipe} "
f"elapsed={elapsed:.1f}s exit={code} passages={passages} "
f"profiles={mouths} routes={routes} {'PASS' if ok else 'FAIL'} "
f"log={logfile}", flush=True)
summary = OUT / "passage-audits.csv"
summary.write_text(
"case,recipe,compiler,elapsed_seconds,exit_code,passages,mouth_profiles,routes,status\n" +
"".join(f"{name},{recipe},{compiler},{elapsed:.3f},{code},{passages},{mouths},{routes},"
f"{'PASS' if ok else 'FAIL'}\n"
for name, recipe, compiler, elapsed, code, passages, mouths, routes, ok in rows),
encoding="utf-8")
print(f"[L37PassageAuditSummary] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 24 else 0)
@@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""Export v24 ring meshes at matching LOD windows and retain old controls."""
import csv
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l37"
BIN = OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang"
OWNER = ROOT / "Standalone/data/owner_world.json"
TERRACE = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
RING_SET = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingSetL32.py"
STAMP = re.compile(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=PASS$", re.M)
EXPORT = re.compile(r"^\[MeshSceneExport\].*status=PASS", re.M)
TERRACE_RESULT = re.compile(r"^\[RingTerraceAudit\].*status=(PASS|FAIL)$", re.M)
SET_RESULT = re.compile(r"^\[RingSetAudit\].*status=(PASS|FAIL)$", re.M)
rows = []
failed = False
FI_CENTERS = ((56, -72), (72, -72), (56, -88), (40, -72), (24, 24),
(104, 24), (72, -88), (120, 24), (104, 8), (24, -24))
SW_CENTERS = ((-136, 136), (-56, 152), (-152, 120), (-136, 120),
(-152, 104), (-136, 104), (-40, 152))
LOD_CENTERS = {
"fi-floor": {
2: (16, -144), 3: (224, -160), 4: (-448, 448),
5: (-128, -640), 6: (-1792, 1280), 7: (3584, 1536),
8: (3072, 7168),
},
"sw-ceiling": {
2: (16, -144), 3: (224, -160), 4: (-448, 448),
5: (-128, -640), 6: (-1792, 1280), 7: (2560, -512),
8: (3072, 7168),
},
}
def run(name, command):
log = OUT / f"ring-{name}.log"
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", *map(str, command)]
started = time.monotonic()
try:
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
except subprocess.TimeoutExpired:
result = None
elapsed = time.monotonic() - started
output = log.read_text(encoding="utf-8", errors="replace")
code = result.returncode if result is not None else "TIMEOUT"
print(f"[L37RingStep] name={name} elapsed={elapsed:.1f}s exit={code} log={log}",
flush=True)
return result, output, elapsed
def export(label, recipe, max_level, camera):
mesh = OUT / f"ring-owner-{label}-v24.vfmesh"
command = [BIN, OWNER, "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8",
f"--max-level={max_level}", f"--recipe={recipe}"]
if camera:
command.append(f"--camera={camera}")
command.append(f"--mesh-export={mesh}")
result, output, elapsed = run(f"export-{label}", command)
stamps = STAMP.findall(output)
ok = (result is not None and result.returncode == 0
and stamps == [(recipe, recipe)] and EXPORT.search(output) and mesh.is_file())
print(f"[L37RingExport] scene={label} recipe={recipe} stamp={stamps} "
f"bytes={mesh.stat().st_size if mesh.exists() else 0} "
f"status={'PASS' if ok else 'FAIL'}", flush=True)
return mesh, elapsed, ok
def terrace(label, recipe, surface, mesh, lod, height_min, height_max,
center=(64, -32), radius=32):
result, output, elapsed = run(f"{label}-lod{lod}", ["python3", TERRACE, mesh,
f"--surface={surface}", "--center", str(center[0]), str(center[1]),
f"--radius={radius}",
f"--lod={lod}", f"--height-min-m={height_min}",
f"--height-max-m={height_max}"])
statuses = TERRACE_RESULT.findall(output)
ok = (result is not None and result.returncode == 0 and statuses == ["PASS"])
rows.append((label, recipe, surface, lod, elapsed, ok))
print(f"[L37RingAuditRun] scene={label} recipe={recipe} surface={surface} "
f"lod={lod} center={center} radius={radius} "
f"status={'PASS' if ok else 'FAIL'}", flush=True)
return ok
def lod1_set(label, recipe, surface, mesh, centers, height_min, height_max):
command = ["python3", RING_SET, mesh, f"--surface={surface}"]
for x, y in centers:
command.extend(("--center", str(x), str(y)))
command.extend(("--radius=8", "--lod=1", f"--height-min-m={height_min}",
f"--height-max-m={height_max}"))
result, output, elapsed = run(f"{label}-lod1-set", command)
statuses = SET_RESULT.findall(output)
ok = (result is not None and result.returncode == 0 and statuses == ["PASS"])
rows.append((label, recipe, surface, 1, elapsed, ok))
print(f"[L37RingAuditRun] scene={label} recipe={recipe} surface={surface} "
f"lod=1 windows={len(centers)} status={'PASS' if ok else 'FAIL'}",
flush=True)
return ok
for label, recipe, surface, window_min, window_max, camera0, camera1, centers in (
("fi-floor", "floating-islands", "floor", -122.0, -117.0,
"64,-32,-115,0,-45", "64,-32,-115,0,-45", FI_CENTERS),
("sw-ceiling", "surface-world", "ceiling", -132.0, -124.0,
"64,-32,-136,0,45", "-96,128,-136,0,45", SW_CENTERS)):
low_mesh, _, low_ok = export(f"{label}-lod0", recipe, 0, camera0)
failed |= not low_ok
if low_ok:
failed |= not terrace(label, recipe, surface, low_mesh, 0, window_min, window_max)
detail_mesh, _, detail_ok = export(f"{label}-lod1", recipe, 1, camera1)
failed |= not detail_ok
if detail_ok:
set_min, set_max = ((-121.625, -117.375) if label == "fi-floor"
else (-128.375, -124.125))
failed |= not lod1_set(label, recipe, surface, detail_mesh, centers,
set_min, set_max)
full_mesh, _, full_ok = export(f"{label}-lod2-8", recipe, 8, None)
failed |= not full_ok
if full_ok:
for lod in range(2, 9):
center = LOD_CENTERS[label][lod]
radius = 4 * (1 << lod)
failed |= not terrace(label, recipe, surface, full_mesh, lod,
window_min, window_max, center, radius)
# Keep the established L30 terrace and L31 flat-surface negative controls live.
controls = (
("l30-terrace", ROOT / "Saved/Linux/l32/fi-floor-l30-camera-negative.vfmesh",
-130.0, -110.0),
("l31-flat", ROOT / "Saved/Linux/l31/fi-floor-v19.vfmesh", -122.0, -117.0),
)
for name, mesh, height_min, height_max in controls:
result, output, _ = run(f"control-{name}", ["python3", TERRACE, mesh,
"--surface=floor", "--center", "64", "-32", "--radius=32", "--lod=0",
f"--height-min-m={height_min}", f"--height-max-m={height_max}"])
statuses = TERRACE_RESULT.findall(output)
detected = (result is not None and result.returncode == 1 and statuses == ["FAIL"])
failed |= not detected
print(f"[L37RingControl] name={name} detected={detected} "
f"status={'PASS' if detected else 'FAIL'}", flush=True)
summary = OUT / "ring-audits-l37.csv"
with summary.open("w", newline="", encoding="utf-8") as stream:
writer = csv.writer(stream)
writer.writerow(("scene", "recipe", "surface", "lod", "elapsed_seconds", "status"))
writer.writerows((label, recipe, surface, lod, f"{elapsed:.3f}",
"PASS" if ok else "FAIL")
for label, recipe, surface, lod, elapsed, ok in rows)
print(f"[L37RingSummary] runs={len(rows)} pass={sum(row[-1] for row in rows)} "
f"fail={sum(not row[-1] for row in rows)} controls=2 "
f"status={'FAIL' if failed else 'PASS'} summary={summary}", flush=True)
raise SystemExit(1 if failed or len(rows) != 18 else 0)
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,31 @@
# Research R1 — natural, walkable cave mouths and tunnels
## What the references show
- **Deep Rock Galactic** separates room/exit structure from biome noise and debris; designers test generated variants for traversal and wayfinding. **Minecraft 1.18** distinguishes caverns from spaghetti and noodle tunnels, and its noise caves form entrances where they intersect the surface. These support a planned route plus a terrain-native mouth, not a portal stamp. [Ghost Ship’s cave-generation post](https://store.steampowered.com/news/posts/?appgroupname=Deep+Rock+Galactic&appids=548430&enddate=1729500950&feed=steam_community_announcements), [Mojang 1.18 notes](https://www.minecraft.net/de-de/article/caves---cliffs--part-ii-out-today-java)
- **Astroneer** describes layered on-the-fly noise with separate cave layers. **No Man’s Sky** identifies navigable caves as a goal and documents dual marching-cubes performance work, but does not publish the cave-shape algorithm. Use these as support for deterministic layers and navigation goals, not as evidence of a specific implementation. [Astroneer](https://www.unrealengine.com/spotlights/how-system-era-softworks-leveraged-ue4-to-create-astroneer-s-wonderful-universe?lang=en-US), [NMS terrain](https://www.nomanssky.com/next-update/), [NMS meshing](https://www.nomanssky.com/worlds-part-I-update/)
- **Enshrouded** describes sculpted macro-topology and hand-placed voxel stamps. **Valheim** documents room assets used by its dungeon generator; **Teardown** documents VoxTool for complex voxel terrain assets. These are topology/tooling examples, not disclosed SDF mouth techniques. [Enshrouded](https://enshrouded.com/ja-JP/news/enshrouded-world-design-team-reddit-ama-recap), [Valheim](https://valheim.com/support/modding-faq-for-the-asset-bundle-update-0-217-40/), [Teardown](https://teardowngame.com/voxtool/)
- Cave research separates playable topology from cave appearance. A karst SDF paper models irregular cross-sections and warns that curved sweeps can lose continuity at high curvature; tortuous paths use a union of spheres. Erosion papers model morphology at generation time, not per voxel. [Playable cave systems](https://santosgrueiro.com/papers/2014/2014-Santam-Cave.pdf), [geological cave SDFs](https://diglib.eg.org/items/a4e7985c-af52-4b55-bd1a-eeeff6c47422), [karst erosion](https://www.sciencedirect.com/science/article/pii/S0097849321002132)
These references do not guarantee VoxelForge’s walk-only contract. Keep its deterministic route, 0.34 m × 1.76 m capsule, supported floor, and slope checks authoritative.
## Ranked proposals
| Rank | Proposal and code location | Expected gain | Main risk / cost |
|---|---|---|---|
| **1** | **Limit the carve by a route floor.** Keep grade-safe route/floor data in [`PlanResolvedLayoutPassagesV1`](../../../../Standalone/Core/src/ResolvedLayoutInternal.h); in [`ApplyResolvedStratePassageCarves`](../../../../Standalone/Core/src/ShapeWorldInternal.h), constrain the tunnel void to above that floor and subtract it from native solid. For negative-is-solid fields: `void = max(tubeSdf, floorZ - z)`, then `carvedSolid = max(nativeSolid, -void)`. Blend the floor to native terrain height along route distance. Replace broad decks where native rock supports the route; keep only narrow fallback support where it does not. | Best chance of a worn path that remains part of the crater/rock instead of reading as a platform. The floor stays explicit and testable. | Medium walkability risk where the source has no solid underfoot or where FI coils overlap. Keep capsule, support, open-air, and slope gates. Low-to-moderate per-sample cost. |
| **2** | **Blend the mouth along route distance.** Refine `mouthCarveFade` / `roughSignedDistance` in `ApplyResolvedStratePassageCarves` (around lines 1168–1209) with a local smooth CSG transition between native terrain and bore. Carving uses smooth-max with the negated void field. Keep the floor cap separate. [Smooth-min reference](https://iquilezles.org/articles/smin/). | Removes a visible seam while letting the entrance follow the local landform. | Smooth CSG moves the zero surface and can round off thin rock or support. L36’s broad filter still left a 27° crease against the earlier 20° probe. Keep it local and hold the crease gate before comparing. Small field cost. |
| **3** | **Shape walls, not the walk plane.** Extend `considerCarveSegment` / `VF_FieldPassageWallRelief` (around lines 933–1043) with one bounded, seeded, low-frequency displacement or asymmetric profile, masked by wall orientation and distance from floor. [Domain warping](https://iquilezles.org/articles/warp/), [karst SDFs](https://diglib.eg.org/items/a4e7985c-af52-4b55-bd1a-eeeff6c47422). | Unequal pockets and folds add geology without ribs or a straight tread outline. | Too much detail can narrow capsule clearance or become periodic. One noise term is cheap; nested warps and curved cross-section sweeps are more expensive and need updated slope/Lipschitz bounds. |
| **4** | **Reuse the current spatial culling.** Bound each new mouth feature so `BuildPassageFeatureTileList`, `PassageMayAffectDensity`, and the nearest-segment BVH skip distant voxels. These AABB and route BVHs already exist ([architecture §8.45](../../../../ARCHITECTURE.md)). Lipschitz pruning is safe only with proven bounds that include smooth blend radii and warp derivatives. [SDF pruning research](https://onlinelibrary.wiley.com/doi/10.1111/cgf.70057). | Protects CPU budget with little geometry risk; lowest-risk performance path. | Little visual gain. Full cached/interpolated distance volumes add memory and can move the zero surface; consider only after profiling, with exact evaluation near the walk surface. |
## What not to repeat from L33–L37
- Regular world-space octaves produced visible 8 m / 4 m wall ribs. Keep relief irregular and seeded.
- Per-segment one-sided tread clamps produced floor washboard. Removing support altogether then caused unsupported samples; keep one continuous floor profile.
- The L34 apron, L36 trapezoid, and L37 raised bridge read as built geometry. The bridge SDF is a separate deck primitive in `ShapeWorldInternal.h`; do not widen it to repair cave shape.
- L36’s broad field filter removed most fragments but left a 27° crease, above the earlier 20° gate. Avoid broad blur and do not tune gates after seeing results.
- Wall-only ripple checks missed the floor beat. Keep walk-floor profiles and negative controls for periodic relief and built shapes.
## Recommendation
Prioritize the floor-limited carve and local terrain-to-bore blend. Let `ResolvedLayoutInternal.h` own the deterministic walk route and grade; let native density provide visible support; confine seeded detail to walls and ceiling. The owner has accepted the current L37 crater mouth, so apply this to the remaining bridge and future shaping without reopening that look.
@@ -0,0 +1,32 @@
# Shared notes (researcher R1 <-> implementer L37), newest at bottom
- **Separate cave shape from cave dressing:** Ghost Ship describes a constrained room-and-exit skeleton, then biome-specific surface noise and debris; its designers test generated variants for traversal, wayfinding, visibility and class playability ([official DRG cave-generation post](https://store.steampowered.com/news/posts/?appgroupname=Deep+Rock+Galactic&appids=548430&enddate=1729500950&feed=steam_community_announcements)). For VoxelForge, keep `ResolvedLayoutInternal.h` responsible for the deterministic route / clearance skeleton and keep material-scale irregularity in `ShapeWorldInternal.h`; do not let a wall-noise change move the walk floor or route.
- **Use distinct tunnel and cavern scales:** Minecraft 1.18 explicitly separates large “cheese” rooms from spaghetti and narrow noodle passages, and lets those noise caves form mouths where they intersect the surface ([Mojang 1.18 notes](https://www.minecraft.net/de-de/article/caves---cliffs--part-ii-out-today-java)). This supports a broad, irregular crater-to-bore transition from intersecting fields, while preserving a narrower path-aligned walk lane; a single uniform portal cylinder is the wrong visual abstraction.
- **Layer deterministic noise, with a gameplay mask:** Astroneer’s published terrain system uses layered noise and separate cave layers evaluated on the fly without neighboring-cell dependency ([Unreal Engine interview](https://www.unrealengine.com/spotlights/how-system-era-softworks-leveraged-ue4-to-create-astroneer-s-wonderful-universe?lang=en-US)). This maps to pure seeded coordinate noise in `ShapeWorldInternal.h`, masked by distance-from-route / wall orientation so only the rock contour gets warped; keep the walk surface’s slope and capsule clearance under the existing route constraints.
- **Treat navigation as a hard constraint, then test variety:** DRG’s designers say a structurally valid room can still be unplayable after generator variation and playtest it repeatedly; No Man’s Sky’s patch notes identify navigability as an explicit terrain-generation goal ([DRG](https://store.steampowered.com/news/posts/?appgroupname=Deep+Rock+Galactic&appids=548430&enddate=1729500950&feed=steam_community_announcements), [NMS terrain update](https://www.nomanssky.com/next-update/)). For this code, keep the capsule/support/open-air and max-grade audits as geometry gates across seeded styles; visual naturalness checks supplement, never replace, them.
- **Author macro topology, procedurally fill the in-between:** Enshrouded’s world team describes sculpted rough models and hand-placed voxel stamps for topology between POIs ([official AMA recap](https://enshrouded.com/ja-JP/news/enshrouded-world-design-team-reddit-ama-recap)). Translation: route points and surrounding crater shape should determine the large form; use bounded relief as finish. Noise alone is not a substitute for an intentional entrance profile.
- **Avoid a single axisymmetric tube at the entrance:** The karst SDF work describes passage sections as irregular and asymmetric; for tortuous curves it switches from sweeps to a union of spheres to retain a continuous, 1-Lipschitz field ([Paris et al., *Synthesizing Geologically Coherent Cave Networks*, PDF](https://diglib.eg.org/bitstreams/fd2a57a6-f1fa-4c74-8b3e-9aea2aeb6047/download)). `ShapeWorldInternal.h::considerCarveSegment` already smooths eligible route joins; a later shape pass could add a small asymmetric cross-section / radius profile there, but preserve a conservative bound and leave floor geometry constrained. Risk: true curved cross-section frames and nearest-point evaluation cost more than the present capsule sweep.
- **Use erosion as a shape model, not a per-voxel simulation:** Cave-network research produces natural branching/channel forms with hydraulic flow and erosion simulation ([Pytel & Mann, JCGT](https://jcgt.org/published/0004/02/02/); [Franke & Müller, karst caves](https://www.sciencedirect.com/science/article/pii/S0097849321002132)). Translate the erosion cues (unequal scallops, asymmetric channel widening, larger pockets at bends) into fixed-seed route descriptors or one bounded noise term; do not run flow/erosion iterations in `ApplyResolvedStratePassageCarves`, which is on the per-voxel CPU path.
- **Smooth the solid/void intersection locally and watch the field bound:** Polynomial smooth-min gives a cheap continuous blend for implicit surfaces ([Quilez, smooth minimum](https://iquilezles.org/articles/smin/)); for solid carving, apply the corresponding smooth-max to the solid and negated void field. Restrict it to the opening region and fade it by distance along the path. Smooth CSG shifts the zero-set and may round away a floor or thin rock, so use capsule, support and crease audits after each change.
- **Do not warp the walk plane:** Domain warping samples a field at noise-displaced coordinates ([Quilez, domain warping](https://iquilezles.org/articles/warp/)). It can break periodic wall bands, but its derivative changes the field slope and repeated warps add several noise evaluations; in `ShapeWorldInternal.h` apply only bounded low-frequency displacement to the wall/ceiling contour, masking by wall orientation and distance from the floor, not to the whole carve or tread field.
- **Keep the existing broad phase; use Lipschitz pruning only with proven bounds:** VoxelForge already selects passage candidates per tile with an AABB BVH and bounds the nearest-segment search with a per-passage BVH ([architecture §8.45](../../../ARCHITECTURE.md)). Recent SDF pruning work is explicit that pruning needs exact 1-Lipschitz fields or valid updated bounds for smooth operators ([Barbier et al., *Lipschitz Pruning*, Computer Graphics Forum](https://onlinelibrary.wiley.com/doi/10.1111/cgf.70057)). Keep new fields inside those bounds; do not assume warped/smoothed noise remains 1-Lipschitz. Cached/trilinearly interpolated fields are a later option only if profiling shows repeated exact queries; interpolation can move the zero surface and needs an exact fallback near the walk surface.
- **Bridge code lead for L37:** In the current capture-matching source, `ApplyResolvedStratePassageCarves` unions `PassageBridgeSignedDistance` for `IsPassageShortSpanSegment`; that field is a separate deck with 3.3–3.65 m half-width and about 4 m thickness ([ShapeWorldInternal.h](../../../Standalone/Core/src/ShapeWorldInternal.h), around `PassageBridgeSignedDistance`). Also check `IsPassageColumnSegment`, which routes through the same support-deck path. The render can therefore come from a planned support feature, independent of cave-wall relief; trace the flagged segment back to its bridge/column plan before changing mouth noise.
- **Some examples use authored pieces rather than SDF mouths:** Valheim documents room assets consumed by `DungeonGenerator` ([official modding FAQ](https://valheim.com/support/modding-faq-for-the-asset-bundle-update-0-217-40/)); Teardown documents a voxel asset tool used for complex campaign terrain ([VoxTool](https://teardowngame.com/voxtool/)). These are useful for authored topology and asset tooling, but neither documents a natural carved mouth algorithm. No Man’s Sky publicly promises more navigable cave systems and reports dual marching-cubes performance work, while leaving its cave-shape algorithm unspecified ([NMS terrain update](https://www.nomanssky.com/next-update/), [Worlds Part I](https://www.nomanssky.com/worlds-part-I-update/)); don't infer its internal method.
- **Implementer control, L37:** Disabling the FI short-span deck left the owner ground-mouth and overview JPEGs byte-identical, while the rock-column audit failed on its four short spans. The deck is needed elsewhere in the route and is not the visible crater-mouth bridge; restored it and traced the entrance carve/support path instead.
- **Implementer control, L37:** Passage tread and native-route blend ablations left the crater ridge; disabling the route-to-mouth support handoff broke 18 FI route support samples. Current trace points to the final FloatingIslands surface-route heights: its 17-probe maximum footing and grade envelope lift the approach over the crater. L167 tests centerline footing for that final segment, with route audits still binding.
- **Implementer trace, L37:** `FloatingIslandsInternal.h::ConnectorDensity` adds an arch up to twice the connector thickness (8 m here), so lowering connector endpoints alone leaves a crater-crossing hump. Testing the v171 version removes that arch and seats connectors 1 m below native island tops; the route is still derived and checked from the resulting field.
- **Implementer correction, L37:** The owner FI JSON defaults to `geometryVersion=4`; v171's `>=5` experiment gate was inert and its capture matched v170. The actual L37 fix is gated by `WorldAssemblyEvaluatorVersion>=172`.
- **Implementer result, L37:** The active v172 no-arch/−1 m connector build retained the surface route but its close capture still showed a central fin. L173 tests the previously effective −2 m sink with the arch removed; passage and both route audits remain gates.
- **Implementer result, L37:** L173 removed the visible fin; its four surface-route misses were on the western island-0-to-1 connector at x≈−5…−3.5 m. L174 sinks only links touching anchored island 2 by 2 m, preserving the western connector height.
- **Implementer trace, L37:** L37 ripple diagnostics show later coil queries matching the entrance relief by XY although they are about 55 m below it. The versioned L175 correction also gates the mouth fade to a 4 m vertical window around the entrance floor; the signed carve-distance lerp stays intact.
- **Implementer experiment, L37:** The largest failing ripple window crosses two helix turns only 0.05 m apart horizontally and 4.17 m apart vertically at 3 m radius. L176 tests the 14.54° (still below 15°) route grade to increase turn separation.
- **Implementer result, L37:** L176 reduced wall ripple to 3.01 cm but failed capsule support at one sample and left floor ripple at 2.18 cm. L177 restores the prior grade and tapers the anchored tunnel from 3 m to 2 m radius over 8–16 m from the mouth, after the fixed close camera.
- **Implementer result, L37:** L177's 2 m tunnel radius was rejected: 103 blocked capsule samples and 3.15 cm floor ripple. The final source reverts L176/L177 route experiments and uses the v175 tunnel profile.
- **Implementer correction, L37:** The final v175 close capture still shows a fin, so root-only connector sinking was insufficient. The global −2 m v173 view removed it, while four support misses were confined to island 0–1; v178 keeps that single link at −1 m and sinks every other connector to −2 m.
- **Implementer result, L37:** v178 still shows the fin. L179 tests the island-0-to-1 connector at −1.5 m as a visual/support compromise; all other links stay at −2 m.
- **Implementer result, L37:** L179's capture is byte-identical to the all−2 m v173 control, but route0 has two unsupported samples. L180 tests −1.25 m on the western link to recover those supports while keeping that appearance.
- **Implementer trace, L37:** The crater bridge is the FI passage helix, not the island connector or short-span deck. Current control points put the route floor 5.4 m above native ground at radius 3 m inside the crater; the mesh at radius 7 m is about 0.1–0.3 m below the intended route floor. L185 tests moving the non-spiral FI passage ring from 3 m to mouth-offset+2 m (7 m), onto the native crater rim; the route/capsule/support audits remain gates.
- **Implementer result, L37:** v188 fixes the bridge by placing the FI passage coil at 7 m (the 5 m mouth offset plus 2 m), on the native crater rim, and splitting the FI route legs after the entry into 1.375 m supports. The bridge is absent in the 6 m and 17 m views. FI route/capsule/support/open-air checks pass; floor ripple falls to 1.39 cm (1.5 cm gate), wall ripple passes. Full style/compiler audits still pending.
- **Implementer result, L37 resumed:** v208 passes owner FI styles 0–3. Style 0 uses the terrain-following crater route and crater tread; other FI styles keep the native route/tread profile. The 8.5–12.5 m native mouth filter now applies to every FI passage style; this cleared open-air and style 1/2/3 ripple regressions. Full SW/fixture and compiler matrix still pending.
- Implementer follow-up, L37 v208: a temporary support-off build reproduces the same 25.542° overview sample as the final mesh, so the remaining crease is not the route tread/support. At the 6 m view it is 4.396°; at 17 m the failing sample interval is 0.125 m and projects to 14.34 px at 2560×1440. The 20° overview gate remains a measured failure; no source change was made after the owner accepted the crater appearance.
- Implementer cost, L37 v208: five interleaved pairs completed; median whole-view change was +0.63% surface-world and +2.04% floating-islands, within pair noise.
+135 -4
View File
@@ -817,8 +817,15 @@ namespace StandaloneStreamBenchmark
double periodMetres=0.0,amplitudeMetres=0.0,localPeakAmplitudeMetres=0.0; double periodMetres=0.0,amplitudeMetres=0.0,localPeakAmplitudeMetres=0.0;
double coherentPeriodMetres=0.0,coherentAmplitudeMetres=0.0; double coherentPeriodMetres=0.0,coherentAmplitudeMetres=0.0;
double controlAmplitudeMetres=0.0; double controlAmplitudeMetres=0.0;
double floorPeriodMetres=0.0,floorAmplitudeMetres=0.0;
double floorControlAmplitudeMetres=0.0;
double floorCoherentPeriodMetres=0.0,floorCoherentAmplitudeMetres=0.0;
std::vector<std::array<double,6>> floorPeakProfile;
double floorPeakWindowStartMetres=0.0;
std::uint64_t samples=0,expectedSamples=0; std::uint64_t samples=0,expectedSamples=0;
std::uint64_t floorSamples=0,floorExpectedSamples=0;
bool controlDetected=false,passed=false; bool controlDetected=false,passed=false;
bool floorControlDetected=false;
}; };
constexpr double MaximumFlatRunMetres=8.0; constexpr double MaximumFlatRunMetres=8.0;
constexpr double MinimumWallReliefMetres=0.20; constexpr double MinimumWallReliefMetres=0.20;
@@ -1131,6 +1138,7 @@ namespace StandaloneStreamBenchmark
constexpr double MaximumPeriodMetres=8.0; constexpr double MaximumPeriodMetres=8.0;
constexpr double PeriodStepMetres=0.0625; constexpr double PeriodStepMetres=0.0625;
constexpr double RippleAmplitudeLimitMetres=0.035; constexpr double RippleAmplitudeLimitMetres=0.035;
constexpr double FloorRippleAmplitudeLimitMetres=0.015;
struct FPathPoint struct FPathPoint
{ {
double x=0.0,y=0.0,z=0.0,radius=0.0,distance=0.0; double x=0.0,y=0.0,z=0.0,radius=0.0,distance=0.0;
@@ -1218,6 +1226,26 @@ namespace StandaloneStreamBenchmark
values[i]-mean-slope*x}); values[i]-mean-slope*x});
} }
}; };
const auto appendDetrendedPairs=[](
const std::vector<std::array<double,2>>& values,
std::vector<std::array<double,2>>& output)
{
if(values.size()<64)return;
double meanX=0.0,meanY=0.0;
for(const auto& value:values){meanX+=value[0];meanY+=value[1];}
meanX/=static_cast<double>(values.size());
meanY/=static_cast<double>(values.size());
double xy=0.0,xx=0.0;
for(const auto& value:values)
{
const double x=value[0]-meanX;
xy+=x*(value[1]-meanY);xx+=x*x;
}
const double slope=xx>0.0?xy/xx:0.0;
for(const auto& value:values)
output.push_back({value[0],value[1]-meanY
-slope*(value[0]-meanX)});
};
std::vector<double> beadControl; std::vector<double> beadControl;
beadControl.reserve(static_cast<std::size_t>(WindowLengthMetres/SampleSpacingMetres)+1); beadControl.reserve(static_cast<std::size_t>(WindowLengthMetres/SampleSpacingMetres)+1);
constexpr double BeadSpacingMetres=1.5; constexpr double BeadSpacingMetres=1.5;
@@ -1234,20 +1262,64 @@ namespace StandaloneStreamBenchmark
double ignoredPeriod=0.0; double ignoredPeriod=0.0;
audit.controlAmplitudeMetres=sinusoidAmplitude(beadSamples,ignoredPeriod); audit.controlAmplitudeMetres=sinusoidAmplitude(beadSamples,ignoredPeriod);
audit.controlDetected=audit.controlAmplitudeMetres>=RippleAmplitudeLimitMetres; audit.controlDetected=audit.controlAmplitudeMetres>=RippleAmplitudeLimitMetres;
const std::array<double,4> windowStarts{{0.03,0.31,0.61,0.91}}; audit.floorControlAmplitudeMetres=audit.controlAmplitudeMetres;
audit.floorControlDetected=audit.floorControlAmplitudeMetres
>=FloorRippleAmplitudeLimitMetres;
const std::array<double,5> windowStarts{{0.03,0.31,0.457,0.61,0.91}};
std::vector<std::array<double,2>> coherentSamples; std::vector<std::array<double,2>> coherentSamples;
std::vector<std::array<double,2>> coherentFloorSamples;
for(const double fraction:windowStarts) for(const double fraction:windowStarts)
{ {
const double start=(total-WindowLengthMetres)*fraction; const double start=(total-WindowLengthMetres)*fraction;
std::vector<double> radii; std::vector<double> radii;
radii.reserve(static_cast<std::size_t>(WindowLengthMetres/SampleSpacingMetres)+1); radii.reserve(static_cast<std::size_t>(WindowLengthMetres/SampleSpacingMetres)+1);
std::vector<std::array<double,2>> floorResiduals;
floorResiduals.reserve(static_cast<std::size_t>(WindowLengthMetres/
SampleSpacingMetres)+1);
std::vector<std::array<double,6>> floorDebugSamples;
floorDebugSamples.reserve(static_cast<std::size_t>(WindowLengthMetres/
SampleSpacingMetres)+1);
for(std::size_t sample=0; for(std::size_t sample=0;
sample<=static_cast<std::size_t>(WindowLengthMetres/SampleSpacingMetres); sample<=static_cast<std::size_t>(WindowLengthMetres/SampleSpacingMetres);
++sample) ++sample)
{ {
++audit.expectedSamples; ++audit.expectedSamples;
++audit.floorExpectedSamples;
const double distance=start+static_cast<double>(sample)*SampleSpacingMetres; const double distance=start+static_cast<double>(sample)*SampleSpacingMetres;
const auto pose=interpolate(distance); const auto pose=interpolate(distance);
// Recover the composed walk surface from its zero crossing.
// The support strip is segment-built and is not itself a true
// signed-distance field, so a fixed centerline radius misses
// the stepped top that the player actually walks on.
const double expectedFloor=pose.z-pose.radius+0.20;
constexpr double FloorProbeStepMetres=0.25;
constexpr int FloorProbeCount=7;
double previousDensity=0.0,previousZ=0.0;
bool havePrevious=false,haveFloor=false;
double bestFloor=0.0,bestDistance=std::numeric_limits<double>::infinity();
for(int vertical=0;vertical<FloorProbeCount;++vertical)
{
const double z=expectedFloor-1.0+vertical*FloorProbeStepMetres;
const double density=static_cast<double>(Field.densityAt({
pose.x/coreMetres,pose.y/coreMetres,z/coreMetres}))*coreMetres;
if(havePrevious&&previousDensity<0.0&&density>=0.0)
{
const double denominator=density-previousDensity;
const double crossing=denominator>1.0e-12
?previousZ+(z-previousZ)*(-previousDensity)/denominator:z;
const double candidateDistance=std::abs(crossing-expectedFloor);
if(candidateDistance<bestDistance)
{bestDistance=candidateDistance;bestFloor=crossing;haveFloor=true;}
}
previousDensity=density;previousZ=z;havePrevious=true;
}
if(haveFloor)
{
floorResiduals.push_back({distance,bestFloor-expectedFloor});
floorDebugSamples.push_back({distance,expectedFloor,bestFloor,
pose.x,pose.y,pose.z});
++audit.floorSamples;
}
const auto before=interpolate(std::max(0.0,distance-0.25)); const auto before=interpolate(std::max(0.0,distance-0.25));
const auto after=interpolate(std::min(total,distance+0.25)); const auto after=interpolate(std::min(total,distance+0.25));
const double tangentX=after.x-before.x,tangentY=after.y-before.y; const double tangentX=after.x-before.x,tangentY=after.y-before.y;
@@ -1279,14 +1351,54 @@ namespace StandaloneStreamBenchmark
} }
coherentSamples.insert(coherentSamples.end(),windowSamples.begin(), coherentSamples.insert(coherentSamples.end(),windowSamples.begin(),
windowSamples.end()); windowSamples.end());
double floorWindowPeriod=0.0,floorWindowAmplitude=0.0;
if(floorResiduals.size()>=static_cast<std::size_t>(WindowLengthMetres/
SampleSpacingMetres)*3/4)
{
std::vector<std::array<double,2>> floorWindowSamples;
appendDetrendedPairs(floorResiduals,floorWindowSamples);
floorWindowAmplitude=sinusoidAmplitude(floorWindowSamples,
floorWindowPeriod);
if(floorWindowAmplitude>audit.floorAmplitudeMetres)
{
audit.floorAmplitudeMetres=floorWindowAmplitude;
audit.floorPeriodMetres=floorWindowPeriod;
audit.floorPeakWindowStartMetres=start;
audit.floorPeakProfile=std::move(floorDebugSamples);
}
coherentFloorSamples.insert(coherentFloorSamples.end(),
floorWindowSamples.begin(),floorWindowSamples.end());
}
const double floorWindowCoverage=static_cast<double>(floorResiduals.size())
/(WindowLengthMetres/SampleSpacingMetres+1.0);
double floorResidualMinimum=0.0,floorResidualMaximum=0.0;
if(!floorResiduals.empty())
{
const auto extrema=std::minmax_element(floorResiduals.begin(),
floorResiduals.end(),[](const auto& a,const auto& b)
{return a[1]<b[1];});
floorResidualMinimum=(*extrema.first)[1];
floorResidualMaximum=(*extrema.second)[1];
}
std::printf("[PassageFloorRippleWindow] id=%s start_m=%.3f "
"period_m=%.3f amplitude_m=%.4f coverage=%.3f "
"residual_range_m=%.4f..%.4f threshold_m=0.015\n",
passage.stableId.c_str(),start,floorWindowPeriod,floorWindowAmplitude,
floorWindowCoverage,floorResidualMinimum,floorResidualMaximum);
} }
audit.coherentAmplitudeMetres=sinusoidAmplitude(coherentSamples, audit.coherentAmplitudeMetres=sinusoidAmplitude(coherentSamples,
audit.coherentPeriodMetres); audit.coherentPeriodMetres);
audit.floorCoherentAmplitudeMetres=sinusoidAmplitude(coherentFloorSamples,
audit.floorCoherentPeriodMetres);
audit.amplitudeMetres=audit.localPeakAmplitudeMetres; audit.amplitudeMetres=audit.localPeakAmplitudeMetres;
const double coverage=audit.expectedSamples>0 const double coverage=audit.expectedSamples>0
?static_cast<double>(audit.samples)/audit.expectedSamples:0.0; ?static_cast<double>(audit.samples)/audit.expectedSamples:0.0;
const double floorCoverage=audit.floorExpectedSamples>0
?static_cast<double>(audit.floorSamples)/audit.floorExpectedSamples:0.0;
audit.passed=audit.controlDetected&&coverage>=0.75 audit.passed=audit.controlDetected&&coverage>=0.75
&&audit.localPeakAmplitudeMetres<RippleAmplitudeLimitMetres; &&audit.localPeakAmplitudeMetres<RippleAmplitudeLimitMetres
&&audit.floorControlDetected&&floorCoverage>=0.75
&&audit.floorAmplitudeMetres<FloorRippleAmplitudeLimitMetres;
return audit; return audit;
}; };
const auto looksNatural=[&](double flatRun,double reliefPeakToPeak) const auto looksNatural=[&](double flatRun,double reliefPeakToPeak)
@@ -1908,6 +2020,7 @@ namespace StandaloneStreamBenchmark
}; };
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0; std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0;
std::uint64_t mouthProfilePasses=0,ripplePasses=0,rippleControlsDetected=0; std::uint64_t mouthProfilePasses=0,ripplePasses=0,rippleControlsDetected=0;
std::uint64_t floorRippleControlsDetected=0;
std::uint64_t openAirViolations=0,openAirLegacyViolations=0,openAirSamples=0; std::uint64_t openAirViolations=0,openAirLegacyViolations=0,openAirSamples=0;
std::uint64_t openAirAudits=0,openAirColumnSamples=0,openAirShortSpanSamples=0; std::uint64_t openAirAudits=0,openAirColumnSamples=0,openAirShortSpanSamples=0;
std::uint64_t columnCrossings=0,shortGroundedSpans=0,columnConnectedEnds=0; std::uint64_t columnCrossings=0,shortGroundedSpans=0,columnConnectedEnds=0;
@@ -1990,6 +2103,7 @@ namespace StandaloneStreamBenchmark
mouthProfilePasses+=mouthProfileAudit.passed; mouthProfilePasses+=mouthProfileAudit.passed;
ripplePasses+=rippleAudit.passed; ripplePasses+=rippleAudit.passed;
rippleControlsDetected+=rippleAudit.controlDetected; rippleControlsDetected+=rippleAudit.controlDetected;
floorRippleControlsDetected+=rippleAudit.floorControlDetected;
passagePasses+=passageAudit.passed&&mouthsInRadius&&mouthsInContent passagePasses+=passageAudit.passed&&mouthsInRadius&&mouthsInContent
&&naturalAudit.passed&&mouthProfileAudit.passed&&rippleAudit.passed; &&naturalAudit.passed&&mouthProfileAudit.passed&&rippleAudit.passed;
std::printf("[ResolvedPassageAudit] id=%s interface=%d-%d control_points=%zu center_samples=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu mouth_content=%d/2 mouth_radius=%d/2 walkability=%s\n", std::printf("[ResolvedPassageAudit] id=%s interface=%d-%d control_points=%zu center_samples=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu mouth_content=%d/2 mouth_radius=%d/2 walkability=%s\n",
@@ -2016,7 +2130,7 @@ namespace StandaloneStreamBenchmark
mouthProfileAudit.wallFlatStepRunMetres,mouthProfileAudit.wallStepMetres, mouthProfileAudit.wallFlatStepRunMetres,mouthProfileAudit.wallStepMetres,
PassageProfileSpacingMetres,PassageProfilePlateauMinimumMetres, PassageProfileSpacingMetres,PassageProfilePlateauMinimumMetres,
PassageProfileStepThresholdMetres,mouthProfileAudit.passed?"PASS":"FAIL"); PassageProfileStepThresholdMetres,mouthProfileAudit.passed?"PASS":"FAIL");
std::printf("[PassageRippleAudit] id=%s window_m=32.000 spacing_m=0.125 period_m=%.3f amplitude_m=%.4f amplitude_cm=%.2f coherent_period_m=%.3f coherent_amplitude_m=%.4f coherent_amplitude_cm=%.2f local_peak_amplitude_m=%.4f coverage=%llu/%llu threshold_m=0.035 synthetic_spheres_period_m=1.500 synthetic_spheres_amplitude_m=%.4f synthetic_spheres_detected=%s status=%s\n", std::printf("[PassageRippleAudit] id=%s window_m=32.000 spacing_m=0.125 period_m=%.3f amplitude_m=%.4f amplitude_cm=%.2f coherent_period_m=%.3f coherent_amplitude_m=%.4f coherent_amplitude_cm=%.2f local_peak_amplitude_m=%.4f coverage=%llu/%llu threshold_m=0.035 synthetic_spheres_period_m=1.500 synthetic_spheres_amplitude_m=%.4f synthetic_spheres_detected=%s floor_period_m=%.3f floor_amplitude_m=%.4f floor_amplitude_cm=%.2f floor_coherent_period_m=%.3f floor_coherent_amplitude_m=%.4f floor_coverage=%llu/%llu floor_threshold_m=0.015 floor_beaded_control_amplitude_m=%.4f floor_beaded_control_detected=%s status=%s\n",
passage.stableId.c_str(),rippleAudit.periodMetres, passage.stableId.c_str(),rippleAudit.periodMetres,
rippleAudit.amplitudeMetres,rippleAudit.amplitudeMetres*100.0, rippleAudit.amplitudeMetres,rippleAudit.amplitudeMetres*100.0,
rippleAudit.coherentPeriodMetres,rippleAudit.coherentAmplitudeMetres, rippleAudit.coherentPeriodMetres,rippleAudit.coherentAmplitudeMetres,
@@ -2025,7 +2139,23 @@ namespace StandaloneStreamBenchmark
static_cast<unsigned long long>(rippleAudit.samples), static_cast<unsigned long long>(rippleAudit.samples),
static_cast<unsigned long long>(rippleAudit.expectedSamples), static_cast<unsigned long long>(rippleAudit.expectedSamples),
rippleAudit.controlAmplitudeMetres,rippleAudit.controlDetected?"true":"false", rippleAudit.controlAmplitudeMetres,rippleAudit.controlDetected?"true":"false",
rippleAudit.floorPeriodMetres,rippleAudit.floorAmplitudeMetres,
rippleAudit.floorAmplitudeMetres*100.0,
rippleAudit.floorCoherentPeriodMetres,rippleAudit.floorCoherentAmplitudeMetres,
static_cast<unsigned long long>(rippleAudit.floorSamples),
static_cast<unsigned long long>(rippleAudit.floorExpectedSamples),
rippleAudit.floorControlAmplitudeMetres,
rippleAudit.floorControlDetected?"true":"false",
rippleAudit.passed?"PASS":"FAIL"); rippleAudit.passed?"PASS":"FAIL");
for(std::size_t sample=0;sample<rippleAudit.floorPeakProfile.size();sample+=4)
{
const auto& value=rippleAudit.floorPeakProfile[sample];
std::printf("[PassageFloorRippleSample] id=%s distance_m=%.3f "
"expected_floor_m=%.4f sampled_floor_m=%.4f residual_m=%.4f "
"route_xyz_m=(%.3f,%.3f,%.3f)\n",
passage.stableId.c_str(),value[0],value[1],value[2],value[2]-value[1],
value[3],value[4],value[5]);
}
std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%llu column_samples=%llu short_span_samples=%llu l24_ungated_violations=%llu route_support_samples=%llu route_support_violations=%llu spacing_m=%.3f status=%s\n", std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%llu column_samples=%llu short_span_samples=%llu l24_ungated_violations=%llu route_support_samples=%llu route_support_violations=%llu spacing_m=%.3f status=%s\n",
passage.stableId.c_str(),static_cast<unsigned long long>(openAirAudit.supportSamples), passage.stableId.c_str(),static_cast<unsigned long long>(openAirAudit.supportSamples),
static_cast<unsigned long long>(openAirAudit.violations), static_cast<unsigned long long>(openAirAudit.violations),
@@ -2340,10 +2470,11 @@ namespace StandaloneStreamBenchmark
PassageProfilePlateauMinimumMetres,PassageProfileStepThresholdMetres, PassageProfilePlateauMinimumMetres,PassageProfileStepThresholdMetres,
l23TerraceControlDetected?"DETECTED":"MISSED", l23TerraceControlDetected?"DETECTED":"MISSED",
mouthProfilePasses==passageCount&&l23TerraceControlDetected?"PASS":"FAIL"); mouthProfilePasses==passageCount&&l23TerraceControlDetected?"PASS":"FAIL");
std::printf("[PassageRippleAuditSummary] passed=%llu/%llu synthetic_spheres_negative_control=%s max_amplitude_m=0.035 status=%s\n", std::printf("[PassageRippleAuditSummary] passed=%llu/%llu synthetic_spheres_negative_control=%s max_amplitude_m=0.035 floor_beaded_negative_control=%s max_floor_amplitude_m=0.015 status=%s\n",
static_cast<unsigned long long>(ripplePasses), static_cast<unsigned long long>(ripplePasses),
static_cast<unsigned long long>(passageCount), static_cast<unsigned long long>(passageCount),
rippleControlsDetected==passageCount?"DETECTED":"MISSED", rippleControlsDetected==passageCount?"DETECTED":"MISSED",
floorRippleControlsDetected==passageCount?"DETECTED":"MISSED",
ripplePasses==passageCount?"PASS":"FAIL"); ripplePasses==passageCount?"PASS":"FAIL");
const bool auditPassed=passageCount>0&&passagePasses==passageCount const bool auditPassed=passageCount>0&&passagePasses==passageCount
&&naturalPasses==passageCount&&l23SmoothWallDetected &&naturalPasses==passageCount&&l23SmoothWallDetected
@@ -743,6 +743,7 @@ namespace StandaloneWorldAssemblyPrototype
const FStratePassageConfig& passage=islandSlot->Definition->PassageConfig; const FStratePassageConfig& passage=islandSlot->Definition->PassageConfig;
recipe.seed=static_cast<std::uint32_t>(owner.Seed); recipe.seed=static_cast<std::uint32_t>(owner.Seed);
recipe.geometryVersion=8; recipe.geometryVersion=8;
recipe.terrainFollowingRoute=static_cast<std::uint32_t>(passage.Style)==0;
recipe.ownerVoxelSizeMicrons=owner.OwnerVoxelSizeMicrons; recipe.ownerVoxelSizeMicrons=owner.OwnerVoxelSizeMicrons;
recipe.coreVoxelSizeMicrons=owner.CoreVoxelSizeMicrons; recipe.coreVoxelSizeMicrons=owner.CoreVoxelSizeMicrons;
recipe.ownerToCoreScaleNumerator=owner.OwnerVoxelSizeMicrons/static_cast<std::uint32_t>(divisor); recipe.ownerToCoreScaleNumerator=owner.OwnerVoxelSizeMicrons/static_cast<std::uint32_t>(divisor);