Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54ba1b49ab |
No files matched your search
+25
-21
@@ -2265,32 +2265,36 @@ mouth and off the walk route. It stays disabled by default. Active records carry
|
||||
L43's report records the off/on hashes, walk and interface audits, outside-stamp timing, tier
|
||||
results, captures and any failed runs.
|
||||
|
||||
### 8.54 Opt-in native lattice field — World Assembler M3 R26 slice 1 (Linux L58)
|
||||
### 8.54 Opt-in native lattice field — World Assembler M3 R26 slices 1–2 (Linux L58–L59)
|
||||
|
||||
`Core.Lattice.PrimitiveField` is a version-1 recipe selected only for one Maze or VerticalShafts
|
||||
`Core.Lattice.PrimitiveField` is an opt-in recipe selected only for one Maze or VerticalShafts
|
||||
strate by `--recipe=lattice --lattice-strate=N`. The legacy generator assets and their default
|
||||
dispatch stay unchanged. Planning first binds the actual incoming lower mouth and outgoing upper
|
||||
mouth from the resolved passage records, then creates arrival/exit overlap corridors, a mandatory
|
||||
four-turn helix and the maze spine; two deterministic dead ends are added after that route. The
|
||||
resolved room and edge seeds use `(world seed, strate, recipe version, stable ID)`. An audit control
|
||||
translates the helix 20 m away from both route junctions to make the shaft network orphaned.
|
||||
dispatch stay unchanged. Version 2 binds the actual incoming lower mouth and outgoing upper mouth,
|
||||
then creates arrival/exit overlap corridors, an open vertical shaft and the maze spine; two
|
||||
deterministic dead ends are added after that route. Resolved room and edge seeds use
|
||||
`(world seed, strate, recipe version, stable ID)`. The orphan control translates the shaft 20 m
|
||||
away from both route junctions and must fail reachability.
|
||||
|
||||
The prototype helix uses a 6 m centreline radius, 3 m clear width, 5 m pitch, four turns and 2.6 m
|
||||
finished floor-to-soffit height. Its centreline descends 20 m over roughly 151 m; the inner-edge
|
||||
grade is about 10.0 degrees. The 2.4 m floor-wall smooth-max gives an isolated ideal fillet radius
|
||||
of 0.8485 m. A separate 0.4 m feature-union blend prevents adjoining ramps from erasing the tread.
|
||||
The swept-clear diameter is 15 m; the conservative padded feature envelope is about 20.6 m (the
|
||||
approved approximately 21 m site envelope).
|
||||
The version-2 shaft core is a vertical cylinder of 4.5 m radius. Its air continues 8 m above the
|
||||
top tread and 2 m below the bottom tread. The ramp winds around the core at a 6 m centreline radius,
|
||||
with 3 m clear width, 5 m pitch and four turns. The open annular shaft air joins the core air, while
|
||||
one-metre support slabs follow the helix under each tread. This leaves a 4 m vertical gap between
|
||||
one tread and the underside of the next. The ledge's inner edge is open to the well; the route
|
||||
centreline stays 1.5 m from the drop and leaves 1.16 m beyond the 0.34 m capsule radius. Its inner
|
||||
edge grade is about 10.0 degrees. The maze retains 2.6 m room headroom. The shaft's padded outer
|
||||
design envelope is 21 m. The 1.9 m floor-wall smooth-max gives an isolated ideal fillet radius of
|
||||
0.6718 m; the separate 0.05 m feature-union blend keeps route junctions connected.
|
||||
|
||||
The layout reuses the resolved cavern graph vocabulary. A per-strate opt-in selects the native
|
||||
negative-solid/positive-air field; canonical feature order is stable-ID order. Room/corridor bounds
|
||||
are indexed by the graph BVH and polyline segments have a nested deterministic BVH for density and
|
||||
interval queries. Box classification stays conservative, and `isTileShapeFree` refuses any tile
|
||||
that intersects the graph. The L58 pipeline audits the composed field from actual mouth to mouth
|
||||
with a 0.34 m by 1.76 m capsule, per-station support, 2.2 m clearance and a 15 degree grade limit;
|
||||
it also checks the orphan control, brute-force interval samples and the composed wall-foot fillet.
|
||||
The Linux58 report contains the measured reachability, surface, peer-determinism, timing and capture
|
||||
results; the code path is not promoted to legacy Maze/VerticalShafts generation.
|
||||
negative-solid/positive-air field; canonical feature order is stable-ID order. The ring-shaped shaft
|
||||
air and its support layers use an analytic helix lookup instead of testing every polyline segment
|
||||
per density sample. The core cylinder and annular wall have conservative interval bounds; false
|
||||
all-air and all-solid controls remain rejected, and `isTileShapeFree` refuses occupied tiles. The
|
||||
L59 pipeline checks the composed field from actual mouth to mouth with a 0.34 m by 1.76 m capsule,
|
||||
support at every station, 2.2 m clearance and a 15 degree grade limit. It also checks the orphan
|
||||
control, whole-surface ramp lip/wall-foot/underside, brute-force interval samples and the composed
|
||||
wall-foot fillet. See the Linux58 and Linux59 reports for evidence. This code path is not promoted
|
||||
to legacy Maze/VerticalShafts generation.
|
||||
|
||||
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||
|
||||
|
||||
+11
-10
@@ -170,8 +170,8 @@ remains `0`, and lateral regions remain gated off.
|
||||
| `../../VoxelForgeCore/Private/ModuleEntry/` | Unreal module entry and an isolated `CoreMinimal.h` shim for the actual engine header. |
|
||||
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Standard-library-only public Core API. `VOXELFORGECORE_API` exports the API from the Unreal module and is empty for standalone builds. |
|
||||
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV3`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, honored transition settings, deterministic per-column transition bands, recipe anchors, director-planned passages, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40–8.42, §8.46. |
|
||||
| `ResolvedLayoutStratePassageCapabilitiesV1::{hasNativeCaveField,hasNativeLatticeField}`, `IsNativeLatticeFieldStratum`, `HasNativeGraphField` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `Standalone/Core/src/ResolvedLayoutInternal.h` | Explicit per-strate native graph capability; `Core.Lattice.PrimitiveField` is restricted to Maze/VerticalShafts recipe version 1. Lattice seeds include world seed, strate, recipe version and stable feature ID; explicit polyline records keep their endpoints and receive conservative bounds. §8.54. |
|
||||
| `FCavernRuntimeFeature::FSegmentNode`, `ApplyResolvedNativeCavesField`, `ClassifyNativeCaveFieldBox` | `Standalone/Core/src/ShapeWorldInternal.h` | A deterministic nested segment BVH accelerates native graph distance and conservative interval evaluation; the outer graph BVH still supplies tile candidates and protects `isTileShapeFree`. The 0.4 m feature-union width is separate from the floor fillet width. §8.54. |
|
||||
| `NativeLatticeFieldRecipeVersion`, `IsNativeLatticeFieldStratum`, `LayoutLatticeFeatureSeed` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `Standalone/Core/src/ResolvedLayoutInternal.h` | Explicit Maze/VerticalShafts opt-in at lattice recipe version 2; stable feature seeds include world seed, strate, recipe version and feature ID. §8.54. |
|
||||
| `FCavernRuntimeFeature::openShaftRamp`, `ApplyResolvedNativeCavesField`, `ClassifyNativeCaveFieldBox` | `Standalone/Core/src/ShapeWorldInternal.h` | Analytic open shaft ring, helical support lookup, exact cylinder core bounds and conservative interval proofs; the outer graph BVH supplies tile candidates and protects `isTileShapeFree`. §8.54. |
|
||||
| `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. |
|
||||
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
|
||||
| Step-3 scale and game validation | `step3-scale` measures the same 519³ and 259³ six-worker lattices at 12.5 cm. The cached medians are 7.347496 s and 1.169567 s respectively; determinism and brick sound-proof audits pass. `step3-game` routes in-game generation through the brick evaluator and records tile timings, spawn fill, moving frames, and profile-switch rebuilds. The visible D3D12 run measured tile generation p50/p95/max 3.326/31.045/169.867 ms, full-detail L0 fill 12.369 s for 2,765 tiles, and moving-frame p50/p95/p99/max 4.856/8.922/13.090/80.575 ms. Switching profiles rebuilt 5,815 loaded tiles. Both high-resolution screenshot requests were logged, but the harness found only one fresh PNG and it is a blank pale viewport; no lit comparison capture is accepted. Raw output: `Saved/TestHarness/step3_game_visible_final`; scale output: `Saved/Research/step3/scale/step3_scale_cacheperf_v1`. |
|
||||
@@ -1310,18 +1310,19 @@ See ARCHITECTURE §8.53 and
|
||||
[`Standalone/Research/WorldAssemblerM3/Linux43/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux43/REPORT.md)
|
||||
for layout identities, owner hashes, outside-box timing, audits and captures.
|
||||
|
||||
### 3.36 Native lattice field — World Assembler M3 R26 slice 1 (Linux L58)
|
||||
### 3.36 Native lattice field — World Assembler M3 R26 slices 1–2 (Linux L58–L59)
|
||||
|
||||
| Symbol | Canonical file | Role |
|
||||
|---|---|---|
|
||||
| `IsNativeLatticeFieldStratum`, `LayoutLatticeFeatureSeed`, `ResolvedCavernRoomV1::unionWidthMicrometres`, `ResolvedCavernEdgeV1::unionWidthMicrometres` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/ResolvedLayoutInternal.h` | Versioned Maze/VerticalShafts opt-in and deterministic seed from `(seed, strate, recipe version, stable ID)`; optional union width preserves prior cave JSON when unset. |
|
||||
| `MakeNativeLatticeSpine`, `--recipe=lattice`, `--lattice-strate=N` | `Standalone/Source/StreamBenchmarkHeadless.cpp` | Resolves actual adjacent mouths, builds a mandatory four-turn helix and maze spine, then adds two seeded dead ends; `--lattice-orphan-control` moves the helix away from both junctions. |
|
||||
| `AuditNativeLatticeField` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples the final field along the mouth-to-mouth capsule route, checking support, headroom, grade, orphan rejection, composed fillet, interval signs and feature-tile exclusion. |
|
||||
| `BuildLatticeFixtureL58.py`, `CaptureNativeLatticeL58.py`, `AuditLatticeSurfaceL58.py` | `Standalone/Research/WorldAssemblerM3/Linux58/` | Isolated Maze strate fixture, four reviewed captures and the L54 whole-surface triangle audit adapted to explicit native polylines. |
|
||||
| `NativeLatticeFieldRecipeVersion`, `IsNativeLatticeFieldStratum`, `LayoutLatticeFeatureSeed` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/ResolvedLayoutInternal.h` | Version-2 Maze/VerticalShafts opt-in and deterministic seed from `(seed, strate, recipe version, stable ID)`. |
|
||||
| `MakeNativeLatticeSpine`, `--recipe=lattice`, `--lattice-strate=N` | `Standalone/Source/StreamBenchmarkHeadless.cpp` | Resolves actual adjacent mouths, builds the radius-4.5 m open shaft core, supported four-turn wall ledge and maze spine, then adds two seeded dead ends; the orphan control translates the shaft away from both junctions. |
|
||||
| `AuditNativeLatticeField` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples the final field along the mouth-to-mouth capsule route, checking every-station support, headroom, grade, orphan rejection, composed fillet, shaft air/wall intervals and tile exclusion. |
|
||||
| `BuildLatticeFixtureL59.py`, `CaptureNativeLatticeL59.py`, `AuditLatticeSurfaceL59.py` | `Standalone/Research/WorldAssemblerM3/Linux59/` | Isolated Maze fixture, four JPEG views of the open shaft and maze, and L54 whole-surface audit extended to include the ramp underside. |
|
||||
|
||||
See ARCHITECTURE §8.54 and
|
||||
[`Standalone/Research/WorldAssemblerM3/Linux58/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux58/REPORT.md)
|
||||
for resolved records, final-field reachability, controls, timing, hashes and captures.
|
||||
See ARCHITECTURE §8.54 and the
|
||||
[`Linux59 report`](Standalone/Research/WorldAssemblerM3/Linux59/REPORT.md) for the open shaft's
|
||||
measured geometry, final-field reachability, controls, timing, hashes and captures. The L58 report
|
||||
retains the first-slice evidence.
|
||||
|
||||
## 4. The density pipeline (most-edited hot path)
|
||||
|
||||
|
||||
@@ -1004,11 +1004,13 @@ namespace VoxelForgeCore
|
||||
&&stratum.recipeVersion==1;
|
||||
}
|
||||
|
||||
inline constexpr std::uint32_t NativeLatticeFieldRecipeVersion=2;
|
||||
|
||||
inline bool IsNativeLatticeFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept
|
||||
{
|
||||
return (stratum.generatorKind=="Maze"||stratum.generatorKind=="VerticalShafts")
|
||||
&&stratum.recipeId=="Core.Lattice.PrimitiveField"
|
||||
&&stratum.recipeVersion==1;
|
||||
&&stratum.recipeVersion==NativeLatticeFieldRecipeVersion;
|
||||
}
|
||||
|
||||
inline bool HasNativeGraphField(const ResolvedLayoutStratumV2& stratum) noexcept
|
||||
|
||||
@@ -1901,7 +1901,8 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
// The shared JSON value type stores numbers as IEEE-754 doubles. Keep the
|
||||
// serialized seed in the exact integer range, as the stamp vocabulary does.
|
||||
room.irregularitySeed=(isNativeLatticeStrate(room.strateIndex)
|
||||
?LayoutLatticeFeatureSeed(seed,room.strateIndex,1,room.stableId)
|
||||
?LayoutLatticeFeatureSeed(seed,room.strateIndex,
|
||||
NativeLatticeFieldRecipeVersion,room.stableId)
|
||||
:LayoutStampSeed(seed,room.strateIndex,room.stableId))
|
||||
&0x001fffffffffffffull;
|
||||
const std::int64_t pad=room.smoothWidthMicrometres
|
||||
@@ -1918,7 +1919,8 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
{
|
||||
const bool nativeLattice=isNativeLatticeStrate(edge.strateIndex);
|
||||
edge.irregularitySeed=(nativeLattice
|
||||
?LayoutLatticeFeatureSeed(seed,edge.strateIndex,1,edge.stableId)
|
||||
?LayoutLatticeFeatureSeed(seed,edge.strateIndex,
|
||||
NativeLatticeFieldRecipeVersion,edge.stableId)
|
||||
:LayoutStampSeed(seed,edge.strateIndex,edge.stableId))
|
||||
&0x001fffffffffffffull;
|
||||
using FCurvePoint=std::array<double,3>;
|
||||
|
||||
@@ -3332,6 +3332,14 @@ private:
|
||||
const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr;
|
||||
const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr;
|
||||
bool nativeGraph=false;
|
||||
bool openShaftRamp=false;
|
||||
std::array<double,2> openShaftCenterMetres{};
|
||||
std::array<double,2> openShaftStartDirection{1.0,0.0};
|
||||
double openShaftCenterlineRadiusMetres=0.0;
|
||||
double openShaftStartFloorMetres=0.0;
|
||||
double openShaftPitchPerTurnMetres=0.0;
|
||||
double openShaftTurnCount=0.0;
|
||||
double openShaftRotationSign=1.0;
|
||||
std::array<std::int64_t,3> boundsMinimumMicrometres{},boundsMaximumMicrometres{};
|
||||
std::array<double,3> centerMetres{},halfExtentsMetres{};
|
||||
struct FSegment
|
||||
@@ -3792,6 +3800,15 @@ private:
|
||||
:found->passageCapabilities.hasNativeLatticeField);
|
||||
};
|
||||
const float micronsPerVoxel=static_cast<float>(Owner->CoreVoxelSizeMicrons);
|
||||
const auto shaftCoreRoom=std::find_if(ResolvedLayout->cavernRooms.begin(),
|
||||
ResolvedLayout->cavernRooms.end(),[](const auto& room)
|
||||
{return room.stableId=="lattice/spine/shaft-core-v2";});
|
||||
std::array<double,2> shaftCoreCenter{};
|
||||
if(shaftCoreRoom!=ResolvedLayout->cavernRooms.end())
|
||||
{
|
||||
shaftCoreCenter={shaftCoreRoom->centerMicrometres[0]/1.0e6,
|
||||
shaftCoreRoom->centerMicrometres[1]/1.0e6};
|
||||
}
|
||||
for(const auto& room:ResolvedLayout->cavernRooms)
|
||||
{
|
||||
FCavernRuntimeFeature runtime;runtime.room=&room;
|
||||
@@ -3822,6 +3839,11 @@ private:
|
||||
{
|
||||
FCavernRuntimeFeature runtime;runtime.edge=&edge;
|
||||
runtime.nativeGraph=isNativeGraphStrate(edge.strateIndex);
|
||||
runtime.openShaftRamp=runtime.nativeGraph
|
||||
&&edge.stableId=="lattice/spine/01-helix-ramp-v1"
|
||||
&&shaftCoreRoom!=ResolvedLayout->cavernRooms.end();
|
||||
if(runtime.openShaftRamp)
|
||||
runtime.openShaftCenterMetres=shaftCoreCenter;
|
||||
runtime.boundsMinimumMicrometres=edge.boundsMinimumMicrometres;
|
||||
runtime.boundsMaximumMicrometres=edge.boundsMaximumMicrometres;
|
||||
for(std::size_t point=0;point<4;++point)
|
||||
@@ -3865,6 +3887,37 @@ private:
|
||||
std::abs((segment.end[2]-segment.start[2])/horizontal));
|
||||
runtime.segments.push_back(segment);
|
||||
}
|
||||
if(runtime.openShaftRamp&&!runtime.segments.empty())
|
||||
{
|
||||
const auto& first=runtime.segments.front().start;
|
||||
const auto& last=runtime.segments.back().end;
|
||||
const double startX=first[0]-shaftCoreCenter[0];
|
||||
const double startY=first[1]-shaftCoreCenter[1];
|
||||
runtime.openShaftCenterlineRadiusMetres=std::hypot(startX,startY);
|
||||
if(runtime.openShaftCenterlineRadiusMetres>1.0e-9)
|
||||
runtime.openShaftStartDirection={
|
||||
startX/runtime.openShaftCenterlineRadiusMetres,
|
||||
startY/runtime.openShaftCenterlineRadiusMetres};
|
||||
runtime.openShaftStartFloorMetres=first[2];
|
||||
double totalAngle=0.0;
|
||||
for(std::size_t segment=0;segment<runtime.segments.size();++segment)
|
||||
{
|
||||
const auto& previous=runtime.segments[segment].start;
|
||||
const auto& current=runtime.segments[segment].end;
|
||||
const double previousX=previous[0]-shaftCoreCenter[0];
|
||||
const double previousY=previous[1]-shaftCoreCenter[1];
|
||||
const double currentX=current[0]-shaftCoreCenter[0];
|
||||
const double currentY=current[1]-shaftCoreCenter[1];
|
||||
totalAngle+=std::atan2(previousX*currentY-previousY*currentX,
|
||||
previousX*currentX+previousY*currentY);
|
||||
}
|
||||
constexpr double TwoPi=6.283185307179586476925286766559;
|
||||
runtime.openShaftTurnCount=std::max(1.0,
|
||||
std::round(std::abs(totalAngle)/TwoPi));
|
||||
runtime.openShaftRotationSign=totalAngle<0.0?-1.0:1.0;
|
||||
runtime.openShaftPitchPerTurnMetres=(first[2]-last[2])
|
||||
/runtime.openShaftTurnCount;
|
||||
}
|
||||
runtime.segmentOrder.resize(runtime.segments.size());
|
||||
for(std::size_t index=0;index<runtime.segmentOrder.size();++index)
|
||||
runtime.segmentOrder[index]=static_cast<std::uint32_t>(index);
|
||||
@@ -5245,8 +5298,49 @@ public:
|
||||
const std::int32_t featureStrate=feature.room?feature.room->strateIndex
|
||||
:feature.edge?feature.edge->strateIndex:-1;
|
||||
if(featureStrate!=strateIndex)continue;
|
||||
bool featureOverlapsBox=true,boxWithinFeatureBounds=true;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double featureMinimum=feature.boundsMinimumMicrometres[axis]/1.0e6;
|
||||
const double featureMaximum=feature.boundsMaximumMicrometres[axis]/1.0e6;
|
||||
if(hi[axis]<featureMinimum||lo[axis]>featureMaximum)
|
||||
{featureOverlapsBox=false;break;}
|
||||
boxWithinFeatureBounds=boxWithinFeatureBounds
|
||||
&&lo[axis]>=featureMinimum&&hi[axis]<=featureMaximum;
|
||||
}
|
||||
if(!featureOverlapsBox)continue;
|
||||
// The point field omits a record outside its resolved bounds. A partial
|
||||
// overlap cannot reuse the record's unbounded SDF for an all-air proof.
|
||||
if(!boxWithinFeatureBounds)return {0.0,0.0,false};
|
||||
FMetricInterval featureVoid;
|
||||
bool openHelixSupportCouldApply=true;
|
||||
double openHelixSupportLowerBound=std::numeric_limits<double>::infinity();
|
||||
if(feature.room)
|
||||
{
|
||||
if(nativeLattice
|
||||
&&feature.room->stableId=="lattice/spine/shaft-core-v2")
|
||||
{
|
||||
const double radius=feature.halfExtentsMetres[0];
|
||||
if(!(radius>0.0))return {0.0,0.0,false};
|
||||
double radialMinimumSquared=0.0,radialMaximumSquared=0.0;
|
||||
for(std::size_t axis=0;axis<2;++axis)
|
||||
{
|
||||
const double center=feature.centerMetres[axis];
|
||||
const double deltaMinimum=center<lo[axis]?lo[axis]-center
|
||||
:center>hi[axis]?center-hi[axis]:0.0;
|
||||
const double deltaMaximum=std::max(std::abs(lo[axis]-center),
|
||||
std::abs(hi[axis]-center));
|
||||
radialMinimumSquared+=deltaMinimum*deltaMinimum;
|
||||
radialMaximumSquared+=deltaMaximum*deltaMaximum;
|
||||
}
|
||||
const double radialMinimum=std::sqrt(radialMinimumSquared)-radius;
|
||||
const double radialMaximum=std::sqrt(radialMaximumSquared)-radius;
|
||||
const double floorZ=feature.room->floorMicrometres/1000000.0;
|
||||
const FMetricInterval bottom={floorZ-hi[2],floorZ-lo[2],true};
|
||||
featureVoid={std::max(radialMinimum,bottom.minimum),
|
||||
std::max(radialMaximum,bottom.maximum),true};
|
||||
}
|
||||
else
|
||||
{
|
||||
double normalizedMinimumSquared=0.0,normalizedMaximumSquared=0.0;
|
||||
double meanRadius=0.0;
|
||||
@@ -5272,13 +5366,134 @@ public:
|
||||
featureVoid={SmoothMaxMetres(ellipsoid.minimum,floor.minimum,width),
|
||||
SmoothMaxMetres(ellipsoid.maximum,floor.maximum,width),true};
|
||||
}
|
||||
}
|
||||
else if(feature.edge&&!feature.segments.empty())
|
||||
{
|
||||
const bool openHelixRamp=nativeLattice&&feature.openShaftRamp;
|
||||
const double radius=feature.edge->boreRadiusMicrometres/1000000.0;
|
||||
const double height=feature.edge->boreHeightMicrometres/1000000.0;
|
||||
if(!(radius>0.0&&height>0.0))return {0.0,0.0,false};
|
||||
double tubeMinimum=std::numeric_limits<double>::infinity();
|
||||
double tubeMaximum=std::numeric_limits<double>::infinity();
|
||||
if(openHelixRamp)
|
||||
{
|
||||
double radialMinimumSquared=0.0,radialMaximumSquared=0.0;
|
||||
for(std::size_t axis=0;axis<2;++axis)
|
||||
{
|
||||
const double center=feature.openShaftCenterMetres[axis];
|
||||
const double deltaMinimum=center<lo[axis]?lo[axis]-center
|
||||
:center>hi[axis]?center-hi[axis]:0.0;
|
||||
const double deltaMaximum=std::max(std::abs(lo[axis]-center),
|
||||
std::abs(hi[axis]-center));
|
||||
radialMinimumSquared+=deltaMinimum*deltaMinimum;
|
||||
radialMaximumSquared+=deltaMaximum*deltaMaximum;
|
||||
}
|
||||
const double radialMinimum=std::sqrt(radialMinimumSquared);
|
||||
const double radialMaximum=std::sqrt(radialMaximumSquared);
|
||||
const double centerline=feature.openShaftCenterlineRadiusMetres;
|
||||
const double nearestRingDistance=centerline<radialMinimum
|
||||
?radialMinimum-centerline:centerline>radialMaximum
|
||||
?centerline-radialMaximum:0.0;
|
||||
tubeMinimum=nearestRingDistance-radius;
|
||||
tubeMaximum=std::max(std::abs(radialMinimum-centerline),
|
||||
std::abs(radialMaximum-centerline))-radius;
|
||||
openHelixSupportCouldApply=tubeMinimum<0.0;
|
||||
const double startFloor=feature.openShaftStartFloorMetres;
|
||||
const double pitch=feature.openShaftPitchPerTurnMetres;
|
||||
const double turnCount=feature.openShaftTurnCount;
|
||||
if(!std::isfinite(tubeMinimum)||!std::isfinite(tubeMaximum)
|
||||
||!std::isfinite(startFloor)||!(pitch>0.0)
|
||||
||!(turnCount>=1.0))return {0.0,0.0,false};
|
||||
const FMetricInterval tube={tubeMinimum,tubeMaximum,true};
|
||||
const double grade=pitch/(6.283185307179586476925286766559
|
||||
*feature.openShaftCenterlineRadiusMetres);
|
||||
const double verticalScale=1.0/std::sqrt(1.0+grade*grade);
|
||||
double phaseMinimum=0.0,phaseMaximum=1.0;
|
||||
const double boxCenterX=(lo[0]+hi[0])*0.5;
|
||||
const double boxCenterY=(lo[1]+hi[1])*0.5;
|
||||
const double centerDx=boxCenterX-feature.openShaftCenterMetres[0];
|
||||
const double centerDy=boxCenterY-feature.openShaftCenterMetres[1];
|
||||
const double centerDistance=std::hypot(centerDx,centerDy);
|
||||
const double halfDiagonal=0.5*std::hypot(hi[0]-lo[0],hi[1]-lo[1]);
|
||||
if(centerDistance>halfDiagonal)
|
||||
{
|
||||
const double angle=std::atan2(feature.openShaftRotationSign
|
||||
*(feature.openShaftStartDirection[0]*centerDy
|
||||
-feature.openShaftStartDirection[1]*centerDx),
|
||||
feature.openShaftStartDirection[0]*centerDx
|
||||
+feature.openShaftStartDirection[1]*centerDy);
|
||||
constexpr double TwoPi=6.283185307179586476925286766559;
|
||||
double centerPhase=angle/TwoPi;
|
||||
const double angleRadius=std::asin(std::clamp(
|
||||
halfDiagonal/centerDistance,0.0,1.0));
|
||||
const double phaseRadius=angleRadius/TwoPi;
|
||||
// Point evaluation normalizes phase into [0,1). If this box
|
||||
// crosses the start ray, retain the full phase range; otherwise
|
||||
// unwrap its center into the same range as the point field.
|
||||
if(std::abs(angle)>angleRadius)
|
||||
{
|
||||
if(centerPhase<0.0)centerPhase+=1.0;
|
||||
phaseMinimum=centerPhase-phaseRadius;
|
||||
phaseMaximum=centerPhase+phaseRadius;
|
||||
}
|
||||
}
|
||||
double verticalMinimum=std::numeric_limits<double>::infinity();
|
||||
double verticalMaximum=-std::numeric_limits<double>::infinity();
|
||||
const double coordinateMinimum=(startFloor-hi[2])/pitch-phaseMaximum;
|
||||
const double coordinateMaximum=(startFloor-lo[2])/pitch-phaseMinimum;
|
||||
if(!std::isfinite(coordinateMinimum)||!std::isfinite(coordinateMaximum))
|
||||
return {0.0,0.0,false};
|
||||
const int lastTurn=std::max(0,static_cast<int>(turnCount)-1);
|
||||
const int firstPossibleTurn=std::clamp(
|
||||
static_cast<int>(std::floor(coordinateMinimum)),-1,lastTurn+1);
|
||||
const int lastPossibleTurn=std::clamp(
|
||||
static_cast<int>(std::floor(coordinateMaximum)),-1,lastTurn+1);
|
||||
const auto corridorInterval=[&](int turn)
|
||||
{
|
||||
const double floorMinimum=startFloor-pitch*(turn+phaseMaximum);
|
||||
const double floorMaximum=startFloor-pitch*(turn+phaseMinimum);
|
||||
const double floorToQueryMinimum=(floorMinimum-hi[2])*verticalScale;
|
||||
const double floorToQueryMaximum=(floorMaximum-lo[2])*verticalScale;
|
||||
const double queryToCeilingMinimum=(lo[2]-floorMaximum-height)
|
||||
*verticalScale;
|
||||
const double queryToCeilingMaximum=(hi[2]-floorMinimum-height)
|
||||
*verticalScale;
|
||||
const double turnMinimum=std::max(floorToQueryMinimum,
|
||||
queryToCeilingMinimum);
|
||||
const double turnMaximum=std::max(floorToQueryMaximum,
|
||||
queryToCeilingMaximum);
|
||||
return FMetricInterval{turnMinimum,turnMaximum,true};
|
||||
};
|
||||
for(int turn=firstPossibleTurn;turn<=lastPossibleTurn;++turn)
|
||||
{
|
||||
const int turnA=std::clamp(turn,0,lastTurn);
|
||||
const int turnB=std::clamp(turn+1,0,lastTurn);
|
||||
const FMetricInterval corridorA=corridorInterval(turnA);
|
||||
const FMetricInterval corridorB=corridorInterval(turnB);
|
||||
verticalMinimum=std::min(verticalMinimum,
|
||||
std::min(corridorA.minimum,corridorB.minimum));
|
||||
verticalMaximum=std::max(verticalMaximum,
|
||||
std::min(corridorA.maximum,corridorB.maximum));
|
||||
const double supportDepth=feature.edge->floorSupportDepthMicrometres
|
||||
/1000000.0;
|
||||
const double progressMinimum=turn+phaseMinimum;
|
||||
const double progressMaximum=turn+phaseMaximum;
|
||||
const double floorMinimum=startFloor-pitch*progressMaximum;
|
||||
const double floorMaximum=startFloor-pitch*progressMinimum;
|
||||
const double supportFloorMinimum=(floorMinimum-hi[2])*verticalScale;
|
||||
const double supportUndersideMinimum=(lo[2]-floorMaximum+supportDepth)
|
||||
*verticalScale;
|
||||
openHelixSupportLowerBound=std::min(openHelixSupportLowerBound,
|
||||
std::max({tubeMinimum,supportFloorMinimum,
|
||||
supportUndersideMinimum}));
|
||||
}
|
||||
const FMetricInterval vertical={verticalMinimum,verticalMaximum,true};
|
||||
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid={SmoothMaxMetres(tube.minimum,vertical.minimum,width),
|
||||
SmoothMaxMetres(tube.maximum,vertical.maximum,width),true};
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::array<double,3> inverseScale={1.0/radius,1.0/radius,
|
||||
1.0/height};
|
||||
const std::array<double,3> boxMinimum={lo[0]*inverseScale[0],
|
||||
@@ -5388,6 +5603,7 @@ public:
|
||||
featureVoid={SmoothMaxMetres(tube.minimum,floor.minimum,width),
|
||||
SmoothMaxMetres(tube.maximum,floor.maximum,width),true};
|
||||
}
|
||||
}
|
||||
else return {0.0,0.0,false};
|
||||
if(nativeLattice)
|
||||
{
|
||||
@@ -5401,10 +5617,17 @@ public:
|
||||
&&lo[0]<=feature.boundsMaximumMicrometres[0]/1.0e6
|
||||
&&hi[1]>=feature.boundsMinimumMicrometres[1]/1.0e6
|
||||
&&lo[1]<=feature.boundsMaximumMicrometres[1]/1.0e6;
|
||||
if(supportDepth>0.0&&overlapsXY
|
||||
const bool supportCouldApply=overlapsXY
|
||||
&&(!feature.openShaftRamp||openHelixSupportCouldApply);
|
||||
if(supportDepth>0.0&&supportCouldApply
|
||||
&&hi[2]>=floorMinimum-supportDepth&&lo[2]<=floorMaximum)
|
||||
{
|
||||
double supportLower=std::max(-supportDepth,lo[2]-floorMaximum);
|
||||
if(feature.openShaftRamp)
|
||||
supportLower=openHelixSupportLowerBound;
|
||||
latticeFloorSupportLowerBound=std::min(latticeFloorSupportLowerBound,
|
||||
std::max(-supportDepth,lo[2]-floorMaximum));
|
||||
supportLower);
|
||||
}
|
||||
}
|
||||
if(!std::isfinite(featureVoid.minimum)||!std::isfinite(featureVoid.maximum))
|
||||
return {0.0,0.0,false};
|
||||
@@ -6962,6 +7185,10 @@ private:
|
||||
return;
|
||||
}
|
||||
if(!feature.edge)return;
|
||||
// The open helix is already carved with its exact cylindrical/turn
|
||||
// field above. Its four serialized control points are a compact record
|
||||
// envelope, not a walkable Bezier centerline for this legacy cap.
|
||||
if(feature.openShaftRamp)return;
|
||||
const auto bezier=[&](float t,std::size_t axis)
|
||||
{
|
||||
const float one=1.0f-t;
|
||||
@@ -7098,14 +7325,34 @@ private:
|
||||
const std::int32_t strateIndex=feature.room?feature.room->strateIndex
|
||||
:feature.edge?feature.edge->strateIndex:-1;
|
||||
if(strateIndex!=bound.StrateIndex)continue;
|
||||
bool insideFeatureBounds=true;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
insideFeatureBounds=insideFeatureBounds
|
||||
&&point[axis]*1000000.0>=feature.boundsMinimumMicrometres[axis]
|
||||
&&point[axis]*1000000.0<=feature.boundsMaximumMicrometres[axis];
|
||||
if(!insideFeatureBounds)continue;
|
||||
double featureVoid=std::numeric_limits<double>::infinity();
|
||||
if(feature.room)
|
||||
{
|
||||
if(nativeLattice
|
||||
&&feature.room->stableId=="lattice/spine/shaft-core-v2")
|
||||
{
|
||||
const double radius=feature.halfExtentsMetres[0];
|
||||
if(!(radius>0.0))continue;
|
||||
const double radial=std::hypot(
|
||||
point[0]-feature.centerMetres[0],
|
||||
point[1]-feature.centerMetres[1])-radius;
|
||||
const double bottom=feature.room->floorMicrometres/1000000.0-point[2];
|
||||
featureVoid=std::max(radial,bottom);
|
||||
}
|
||||
else
|
||||
{
|
||||
double normalizedSquared=0.0,meanRadius=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double radius=feature.halfExtentsMetres[axis];
|
||||
if(!(radius>0.0)){normalizedSquared=std::numeric_limits<double>::infinity();break;}
|
||||
if(!(radius>0.0))
|
||||
{normalizedSquared=std::numeric_limits<double>::infinity();break;}
|
||||
const double delta=(point[axis]-feature.centerMetres[axis])/radius;
|
||||
normalizedSquared+=delta*delta;meanRadius+=radius/3.0;
|
||||
}
|
||||
@@ -7114,14 +7361,63 @@ private:
|
||||
const double width=feature.room->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
|
||||
}
|
||||
}
|
||||
else if(feature.edge&&!feature.segments.empty())
|
||||
{
|
||||
const bool openHelixRamp=nativeLattice&&feature.openShaftRamp;
|
||||
double tube=std::numeric_limits<double>::infinity();
|
||||
double nearestFloor=0.0,nearestGrade=0.0,nearestRadial=0.0;
|
||||
double openHelixVerticalSdf=std::numeric_limits<double>::infinity();
|
||||
double openHelixPhase=0.0;
|
||||
int openHelixTurnA=0,openHelixTurnB=0;
|
||||
double openHelixVerticalScale=1.0;
|
||||
const double radius=feature.edge->boreRadiusMicrometres/1000000.0;
|
||||
const double height=feature.edge->boreHeightMicrometres/1000000.0;
|
||||
if(!(radius>0.0&&height>0.0))continue;
|
||||
const double minScale=std::min(radius,height);
|
||||
if(openHelixRamp)
|
||||
{
|
||||
const double dx=point[0]-feature.openShaftCenterMetres[0];
|
||||
const double dy=point[1]-feature.openShaftCenterMetres[1];
|
||||
const double shaftRadial=std::hypot(dx,dy);
|
||||
nearestRadial=std::abs(shaftRadial
|
||||
-feature.openShaftCenterlineRadiusMetres);
|
||||
tube=nearestRadial-radius;
|
||||
const double phase=std::atan2(feature.openShaftRotationSign
|
||||
*(feature.openShaftStartDirection[0]*dy
|
||||
-feature.openShaftStartDirection[1]*dx),
|
||||
feature.openShaftStartDirection[0]*dx
|
||||
+feature.openShaftStartDirection[1]*dy);
|
||||
constexpr double TwoPi=6.283185307179586476925286766559;
|
||||
openHelixPhase=(phase<0.0?phase+TwoPi:phase)/TwoPi;
|
||||
const double pitch=feature.openShaftPitchPerTurnMetres;
|
||||
if(!(pitch>0.0)||!(feature.openShaftTurnCount>=1.0))continue;
|
||||
const double grade=pitch/(TwoPi
|
||||
*feature.openShaftCenterlineRadiusMetres);
|
||||
openHelixVerticalScale=1.0/std::sqrt(1.0+grade*grade);
|
||||
const double turnCoordinate=(feature.openShaftStartFloorMetres-point[2])
|
||||
/pitch-openHelixPhase;
|
||||
const int turnFloor=static_cast<int>(std::floor(turnCoordinate));
|
||||
const int lastTurn=std::max(0,
|
||||
static_cast<int>(feature.openShaftTurnCount)-1);
|
||||
openHelixTurnA=std::clamp(turnFloor,0,lastTurn);
|
||||
openHelixTurnB=std::clamp(turnFloor+1,0,lastTurn);
|
||||
const auto floorForTurn=[&](int turn)
|
||||
{
|
||||
return feature.openShaftStartFloorMetres
|
||||
-pitch*(turn+openHelixPhase);
|
||||
};
|
||||
const auto corridorSdf=[&](int turn)
|
||||
{
|
||||
const double floorZ=floorForTurn(turn);
|
||||
return std::max((floorZ-point[2])*openHelixVerticalScale,
|
||||
(point[2]-(floorZ+height))*openHelixVerticalScale);
|
||||
};
|
||||
openHelixVerticalSdf=std::min(corridorSdf(openHelixTurnA),
|
||||
corridorSdf(openHelixTurnB));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::uint32_t nearestSegment=std::numeric_limits<std::uint32_t>::max();
|
||||
const auto evaluateSegment=[&](std::uint32_t segmentIndex)
|
||||
{
|
||||
@@ -7146,7 +7442,7 @@ private:
|
||||
}
|
||||
const double normalized=std::sqrt(radialSquared/(radius*radius)
|
||||
+vertical*vertical/(height*height));
|
||||
const double distance=(normalized-1.0)*std::min(radius,height);
|
||||
const double distance=(normalized-1.0)*minScale;
|
||||
if(distance<tube||(distance==tube&&segmentIndex<nearestSegment))
|
||||
{
|
||||
tube=distance;nearestSegment=segmentIndex;
|
||||
@@ -7197,16 +7493,42 @@ private:
|
||||
}
|
||||
else for(std::uint32_t segment=0;segment<feature.segments.size();++segment)
|
||||
evaluateSegment(segment);
|
||||
}
|
||||
const double supportDepth=feature.edge->floorSupportDepthMicrometres/1000000.0;
|
||||
if(nativeLattice&&supportDepth>0.0&&nearestRadial<radius-0.12
|
||||
&&point[2]<nearestFloor&&nearestFloor-point[2]<=supportDepth)
|
||||
if(nativeLattice&&supportDepth>0.0)
|
||||
{
|
||||
if(openHelixRamp)
|
||||
{
|
||||
const auto supportSdf=[&](int turn)
|
||||
{
|
||||
const double floorZ=feature.openShaftStartFloorMetres
|
||||
-feature.openShaftPitchPerTurnMetres
|
||||
*(turn+openHelixPhase);
|
||||
const double vertical=std::max(
|
||||
(floorZ-point[2])*openHelixVerticalScale,
|
||||
(point[2]-(floorZ-supportDepth))
|
||||
*openHelixVerticalScale);
|
||||
return std::max(nearestRadial-radius,vertical);
|
||||
};
|
||||
latticeFloorSupportMinimum=std::min(latticeFloorSupportMinimum,
|
||||
std::min(supportSdf(openHelixTurnA),
|
||||
supportSdf(openHelixTurnB)));
|
||||
}
|
||||
else if(nearestRadial<radius-0.12&&point[2]<nearestFloor
|
||||
&&nearestFloor-point[2]<=supportDepth)
|
||||
latticeFloorSupportMinimum=std::min(latticeFloorSupportMinimum,
|
||||
(point[2]-nearestFloor)/std::sqrt(1.0+nearestGrade*nearestGrade));
|
||||
}
|
||||
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
|
||||
if(openHelixRamp)
|
||||
featureVoid=SmoothMaxMetres(tube,openHelixVerticalSdf,width);
|
||||
else
|
||||
{
|
||||
const double floor=(nearestFloor-point[2])
|
||||
/std::sqrt(1.0+nearestGrade*nearestGrade);
|
||||
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid=SmoothMaxMetres(tube,floor,width);
|
||||
}
|
||||
}
|
||||
if(!std::isfinite(featureVoid))continue;
|
||||
if(!std::isfinite(caveVoid))caveVoid=featureVoid;
|
||||
else caveVoid=SmoothMinMetres(caveVoid,featureVoid,
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run L54's triangle audit over L59's open-shaft lattice and controls."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
AUDIT54 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux54/AuditWholeSurfaceL54.py"
|
||||
spec = importlib.util.spec_from_file_location("whole_surface_l54", AUDIT54)
|
||||
audit54 = importlib.util.module_from_spec(spec)
|
||||
assert spec and spec.loader
|
||||
spec.loader.exec_module(audit54)
|
||||
original_make_route = audit54.make_route
|
||||
|
||||
|
||||
def build_segment_bvh(segments: list[tuple]) -> dict:
|
||||
def build(indices: list[int]) -> dict:
|
||||
minimum = tuple(min(min(segments[index][0][axis], segments[index][1][axis])
|
||||
for index in indices) for axis in range(3))
|
||||
maximum = tuple(max(max(segments[index][0][axis], segments[index][1][axis])
|
||||
for index in indices) for axis in range(3))
|
||||
if len(indices) <= 8:
|
||||
return {"minimum": minimum, "maximum": maximum,
|
||||
"segments": tuple(sorted(indices)), "left": None, "right": None}
|
||||
axis = max(range(3), key=lambda value: maximum[value] - minimum[value])
|
||||
indices.sort(key=lambda index: (segments[index][0][axis]
|
||||
+ segments[index][1][axis]) * 0.5)
|
||||
middle = len(indices) // 2
|
||||
return {"minimum": minimum, "maximum": maximum, "segments": None,
|
||||
"left": build(indices[:middle]), "right": build(indices[middle:])}
|
||||
|
||||
if not segments:
|
||||
raise ValueError("lattice audit route has no measurable segments")
|
||||
return build(list(range(len(segments))))
|
||||
|
||||
|
||||
def make_lattice_route(layout: dict, phase_m: float = 0.0) -> dict:
|
||||
edges = sorted((edge for edge in layout.get("cavern_edges", [])
|
||||
if edge["stable_id"] in ("lattice/spine/00-arrival-portal-v1",
|
||||
"lattice/spine/01-helix-ramp-v1", "lattice/spine/99-departure-portal-v1")
|
||||
or edge["stable_id"].startswith("lattice/spine/maze-edge-")),
|
||||
key=lambda edge: (0 if edge["stable_id"].startswith("lattice/spine/00-") else
|
||||
1 if edge["stable_id"].startswith("lattice/spine/01-") else
|
||||
2 if edge["stable_id"].startswith("lattice/spine/maze-edge-") else 3,
|
||||
edge["stable_id"]))
|
||||
if not edges:
|
||||
raise ValueError("lattice audit needs its resolved mandatory spine edges")
|
||||
segments = []
|
||||
routes = []
|
||||
for edge_index, edge in enumerate(edges):
|
||||
points = [[value / 1_000_000.0 for value in row]
|
||||
for row in edge.get("polyline_um", [])]
|
||||
if len(points) < 2:
|
||||
raise ValueError(f"{edge['stable_id']}: explicit path is missing")
|
||||
radius = edge["bore_radius_um"] / 1_000_000.0
|
||||
height = edge["bore_height_um"] / 1_000_000.0
|
||||
support_depth = edge.get("floor_support_depth_um", 0) / 1_000_000.0
|
||||
horizontal_s = [0.0]
|
||||
for a, b in zip(points, points[1:]):
|
||||
horizontal_s.append(horizontal_s[-1] + ((b[0] - a[0]) ** 2
|
||||
+ (b[1] - a[1]) ** 2) ** 0.5)
|
||||
first = points[1]
|
||||
first_dx, first_dy = first[0] - points[0][0], first[1] - points[0][1]
|
||||
first_length = max(1.0e-9, (first_dx * first_dx + first_dy * first_dy) ** 0.5)
|
||||
extension = (points[0][0] - first_dx / first_length * audit54.OPEN_GROUND_EXTENSION_M,
|
||||
points[0][1] - first_dy / first_length * audit54.OPEN_GROUND_EXTENSION_M,
|
||||
points[0][2] - (first[2] - points[0][2]) / first_length
|
||||
* audit54.OPEN_GROUND_EXTENSION_M)
|
||||
route_points = [extension, *points]
|
||||
route_s = [-audit54.OPEN_GROUND_EXTENSION_M, *horizontal_s]
|
||||
for index, (a, b) in enumerate(zip(route_points, route_points[1:])):
|
||||
segments.append((a, b, route_s[index], route_s[index + 1],
|
||||
radius, height, edge_index))
|
||||
routes.append({"edge": edge, "points": points, "radius": radius,
|
||||
"height": height, "support_depth": support_depth,
|
||||
"length": horizontal_s[-1], "index": edge_index})
|
||||
# The shaft core is an open cylindrical volume, not a walkable room. Its
|
||||
# axis-aligned bounds include unrelated rock outside the cylinder and
|
||||
# above the ramp headroom; the helix corridor already includes the inner
|
||||
# wall, lip, foot, and underside where those surfaces meet the route.
|
||||
rooms = [{"room": room,
|
||||
"center": tuple(value / 1_000_000.0 for value in room["center_um"]),
|
||||
"half": tuple(value / 1_000_000.0 for value in room["half_extents_um"]),
|
||||
"floor": room["floor_um"] / 1_000_000.0}
|
||||
for room in sorted(layout.get("cavern_rooms", []), key=lambda row: row["stable_id"])
|
||||
if room.get("stable_id") != "lattice/spine/shaft-core-v2"]
|
||||
return {"segments": segments, "edges": routes, "rooms": rooms, "phase_m": phase_m,
|
||||
"segment_bvh": build_segment_bvh(segments)}
|
||||
|
||||
|
||||
def make_route(layout: dict, phase_m: float = 0.0) -> dict:
|
||||
if any(edge.get("stable_id", "").startswith("lattice/")
|
||||
for edge in layout.get("cavern_edges", [])):
|
||||
return make_lattice_route(layout, phase_m)
|
||||
return original_make_route(layout, phase_m)
|
||||
|
||||
|
||||
def lattice_route_nearest(point: tuple[float, float, float], route: dict):
|
||||
"""Resolve stacked helix turns in 3D, then measure corridor radius in XY."""
|
||||
best = None
|
||||
def consider(segment_index: int) -> None:
|
||||
nonlocal best
|
||||
a, b, s0, s1, radius, height, edge_index = route["segments"][segment_index]
|
||||
dx, dy, dz = (b[axis] - a[axis] for axis in range(3))
|
||||
length_squared = dx * dx + dy * dy + dz * dz
|
||||
t = 0.0 if length_squared <= 1.0e-12 else max(0.0, min(1.0,
|
||||
((point[0] - a[0]) * dx + (point[1] - a[1]) * dy
|
||||
+ (point[2] - a[2]) * dz) / length_squared))
|
||||
x, y, z = (a[axis] + (b[axis] - a[axis]) * t for axis in range(3))
|
||||
distance3d_squared = ((point[0] - x) ** 2 + (point[1] - y) ** 2
|
||||
+ (point[2] - z) ** 2)
|
||||
if (best is None or distance3d_squared < best[0]
|
||||
or (distance3d_squared == best[0] and segment_index < best[1])):
|
||||
horizontal_length = math.hypot(dx, dy)
|
||||
lateral = ((dx * (point[1] - y) - dy * (point[0] - x))
|
||||
/ horizontal_length if horizontal_length > 1.0e-9 else 0.0)
|
||||
best = (distance3d_squared, segment_index,
|
||||
math.hypot(point[0] - x, point[1] - y),
|
||||
s0 + (s1 - s0) * t, z, lateral, radius, height, edge_index,
|
||||
route["edges"][edge_index]["length"])
|
||||
|
||||
def lower_bound_squared(node: dict) -> float:
|
||||
distance_squared = 0.0
|
||||
for axis in range(3):
|
||||
gap = (node["minimum"][axis] - point[axis]
|
||||
if point[axis] < node["minimum"][axis]
|
||||
else point[axis] - node["maximum"][axis]
|
||||
if point[axis] > node["maximum"][axis] else 0.0)
|
||||
distance_squared += gap * gap
|
||||
return distance_squared
|
||||
|
||||
def visit(node: dict) -> None:
|
||||
if best is not None and lower_bound_squared(node) > best[0]:
|
||||
return
|
||||
if node["segments"] is not None:
|
||||
for segment_index in node["segments"]:
|
||||
consider(segment_index)
|
||||
return
|
||||
left, right = node["left"], node["right"]
|
||||
left_bound, right_bound = lower_bound_squared(left), lower_bound_squared(right)
|
||||
if left_bound <= right_bound:
|
||||
visit(left)
|
||||
visit(right)
|
||||
else:
|
||||
visit(right)
|
||||
visit(left)
|
||||
|
||||
visit(route["segment_bvh"])
|
||||
if best is None:
|
||||
raise ValueError("lattice audit route has no measurable segments")
|
||||
return best[2:]
|
||||
|
||||
|
||||
def mark_lattice_region(triangles: list, route: dict) -> None:
|
||||
margin = audit54.CORRIDOR_MARGIN_M
|
||||
extension = audit54.OPEN_GROUND_EXTENSION_M
|
||||
bounds = []
|
||||
for edge in route["edges"]:
|
||||
points = edge["points"]
|
||||
radius, height = edge["radius"], edge["height"]
|
||||
support_depth = edge["support_depth"]
|
||||
bounds.append((
|
||||
min(point[0] for point in points) - radius - margin,
|
||||
max(point[0] for point in points) + radius + margin,
|
||||
min(point[1] for point in points) - radius - margin,
|
||||
max(point[1] for point in points) + radius + margin,
|
||||
min(point[2] for point in points) - support_depth - margin,
|
||||
max(point[2] for point in points) + height + margin))
|
||||
for room in route["rooms"]:
|
||||
center, half, floor = room["center"], room["half"], room["floor"]
|
||||
bounds.append((center[0] - half[0] - margin, center[0] + half[0] + margin,
|
||||
center[1] - half[1] - margin, center[1] + half[1] + margin,
|
||||
floor - margin, floor + 2.0 * half[2] + margin))
|
||||
|
||||
for triangle in triangles:
|
||||
x, y, z = triangle.center
|
||||
if not any(low_x <= x <= high_x and low_y <= y <= high_y
|
||||
and low_z <= z <= high_z
|
||||
for low_x, high_x, low_y, high_y, low_z, high_z in bounds):
|
||||
triangle.in_region = False
|
||||
triangle.reference = False
|
||||
continue
|
||||
nearest = lattice_route_nearest(triangle.center, route)
|
||||
distance, distance_s, floor_z, lateral, radius, height, edge_index, edge_length = nearest
|
||||
support_depth = route["edges"][edge_index]["support_depth"]
|
||||
corridor = (-extension <= distance_s <= edge_length
|
||||
and distance <= radius + margin
|
||||
and floor_z - support_depth - margin <= z <= floor_z + height + margin)
|
||||
room_box = any(abs(x - room["center"][0]) <= room["half"][0] + margin
|
||||
and abs(y - room["center"][1]) <= room["half"][1] + margin
|
||||
and room["floor"] - margin <= z
|
||||
<= room["floor"] + 2.0 * room["half"][2] + margin
|
||||
for room in route["rooms"])
|
||||
triangle.in_region = corridor or room_box
|
||||
inside_room = any(abs(x - room["center"][0]) <= room["half"][0] + 0.45
|
||||
and abs(y - room["center"][1]) <= room["half"][1] + 0.45
|
||||
for room in route["rooms"])
|
||||
outside_bore = distance > radius + 0.45 and not inside_room
|
||||
triangle.reference = (triangle.in_region and outside_bore
|
||||
and triangle.smooth[2] >= 0.45)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--case", nargs=5, action="append", required=True,
|
||||
metavar=("LABEL", "MESH", "LAYOUT_OR_DASH", "EXPECT", "FOCUS"))
|
||||
parser.add_argument("--json", type=Path, required=True)
|
||||
parser.add_argument("--svg-dir", type=Path)
|
||||
parser.add_argument("--phase", type=float, choices=(-0.5, 0.0, 0.5), default=0.0)
|
||||
args = parser.parse_args()
|
||||
audit54.make_route = make_route
|
||||
results = []
|
||||
for label, mesh, layout, expect, focus in args.case:
|
||||
if expect not in ("pass", "fail", "measure") or focus not in ("whole", "floor"):
|
||||
parser.error("EXPECT must be pass/fail/measure and FOCUS must be whole/floor")
|
||||
native_lattice = layout != "-" and any(
|
||||
edge.get("stable_id", "").startswith("lattice/")
|
||||
for edge in json.loads(Path(layout).read_text(encoding="utf-8")).get(
|
||||
"cavern_edges", []))
|
||||
original_margin = audit54.CORRIDOR_MARGIN_M
|
||||
original_extension = audit54.OPEN_GROUND_EXTENSION_M
|
||||
original_nearest = audit54.route_nearest
|
||||
original_mark_region = audit54.mark_region
|
||||
if native_lattice:
|
||||
# Keep the whole-surface test on native records: one core voxel of
|
||||
# tolerance captures their meshed walls while excluding the separate
|
||||
# legacy transition above the resolved mouth.
|
||||
audit54.CORRIDOR_MARGIN_M = 0.25
|
||||
audit54.OPEN_GROUND_EXTENSION_M = 0.0
|
||||
audit54.route_nearest = lattice_route_nearest
|
||||
audit54.mark_region = mark_lattice_region
|
||||
try:
|
||||
results.append(audit54.audit_case(label, Path(mesh), Path(layout), expect,
|
||||
focus, args.svg_dir, args.phase))
|
||||
finally:
|
||||
audit54.CORRIDOR_MARGIN_M = original_margin
|
||||
audit54.OPEN_GROUND_EXTENSION_M = original_extension
|
||||
audit54.route_nearest = original_nearest
|
||||
audit54.mark_region = original_mark_region
|
||||
if native_lattice:
|
||||
print("[L59LatticeAuditScope] margin_m=0.250 ramp_underside_depth_m=1.000 "
|
||||
"outside_mouth_extension_m=0.000 nearest_centerline=3D "
|
||||
"radius_measure=horizontal status=PASS")
|
||||
args.json.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.json.write_text(json.dumps({"audit": "whole-surface-open-shaft-l59-v1",
|
||||
"cases": results}, indent=2) + "\n", encoding="utf-8")
|
||||
failures = [result for result in results if result["status"] != "PASS"]
|
||||
print(f"[L59WholeSurfaceAuditSummary] cases={len(results)} failed={len(failures)} "
|
||||
f"json={args.json} status={'FAIL' if failures else 'PASS'}")
|
||||
return 1 if failures else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Split the owner surface strate around the L59 native lattice fixture."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--owner", type=Path, required=True)
|
||||
parser.add_argument("--out", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
root = json.loads(args.owner.read_text(encoding="utf-8"))
|
||||
strates = root.get("strates")
|
||||
if not isinstance(strates, list):
|
||||
raise ValueError("owner input has no strates array")
|
||||
by_index = {int(row.get("index", -1)): row for row in strates if isinstance(row, dict)}
|
||||
if set(by_index) != {0, 1} or len(strates) != 2:
|
||||
raise ValueError("fixture requires the reviewed two-strate owner input")
|
||||
upper, surface = by_index[0], by_index[1]
|
||||
if (int(upper.get("asset", {}).get("GeneratorType", -1)) != 0
|
||||
or int(surface.get("asset", {}).get("GeneratorType", -1)) != 4):
|
||||
raise ValueError("fixture requires TunnelNetwork over SurfaceWorld")
|
||||
if int(upper.get("passage_config", {}).get("Connections", -1)) != 1:
|
||||
raise ValueError("owner arrival passage count differs from the reviewed input")
|
||||
|
||||
# Keep both resolved mouths in the small synthetic Maze layer. The authored
|
||||
# owner reach ranges are intentionally broad and can place a synthetic mouth
|
||||
# hundreds of metres beyond a four-chunk prototype layer.
|
||||
upper["passage_config"].update({"ReachMin": 32, "ReachMax": 32})
|
||||
upper["asset"]["PassageConfig"].update({"ReachMin": 32, "ReachMax": 32})
|
||||
|
||||
maze = json.loads(json.dumps(surface))
|
||||
maze.update({"index": 1, "top_chunk_z": -16, "bottom_chunk_z": -19,
|
||||
"height_chunks": 4})
|
||||
maze["asset"].update({"StrateName": "L59 Native Lattice Maze",
|
||||
"GeneratorType": 3, "StrateHeightInChunks": 4})
|
||||
maze["asset"].setdefault("MazeParams", {})["BoundarySealThickness"] = 8
|
||||
maze["passage_config"]["Connections"] = 1
|
||||
maze["passage_config"].update({"ReachMin": 32, "ReachMax": 32})
|
||||
maze["asset"]["PassageConfig"].update({"Connections": 1, "ReachMin": 32, "ReachMax": 32})
|
||||
|
||||
lower = json.loads(json.dumps(surface))
|
||||
lower.update({"index": 2, "top_chunk_z": -20, "bottom_chunk_z": -23,
|
||||
"height_chunks": 4})
|
||||
lower["asset"].update({"StrateName": "L59 Lower Surface", "GeneratorType": 4,
|
||||
"StrateHeightInChunks": 4})
|
||||
lower["passage_config"]["Connections"] = 1
|
||||
lower["asset"]["PassageConfig"]["Connections"] = 1
|
||||
|
||||
root["total_strates"] = 3
|
||||
root["strates"] = [upper, maze, lower]
|
||||
# Keep the legacy passage portals proportional to this compact native
|
||||
# prototype; the owner world remains at its authored scale.
|
||||
root.update({"owner_voxel_size_cm": 6.25,
|
||||
"owner_voxel_size_microns": 62500,
|
||||
"core_voxel_size_cm": 25,
|
||||
"core_voxel_size_microns": 250000,
|
||||
"owner_to_core_length_scale": 0.25})
|
||||
args.out.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.out.write_text(json.dumps(root, indent=2, sort_keys=True) + "\n",
|
||||
encoding="utf-8")
|
||||
print(f"[LatticeFixtureBuild] source={args.owner} source_sha256={sha256(args.owner)} "
|
||||
f"fixture={args.out} fixture_sha256={sha256(args.out)} "
|
||||
"strates=0:TunnelNetwork,1:Maze[4 chunks],2:SurfaceWorld[60 chunks] "
|
||||
"owner_input_untouched=true status=PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Export the L59 open-shaft lattice and render four reviewed viewpoints."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
CAPTURE54 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux54/CaptureNativeCavesL54.py"
|
||||
spec = importlib.util.spec_from_file_location("capture_native_caves_l54", CAPTURE54)
|
||||
capture54 = importlib.util.module_from_spec(spec)
|
||||
assert spec and spec.loader
|
||||
spec.loader.exec_module(capture54)
|
||||
run_step = capture54.run_step
|
||||
camera_angles = capture54.camera_angles
|
||||
filter_mesh_y_leq = capture54.filter_mesh_y_leq
|
||||
fmt = capture54.fmt
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def midpoint(points: list[list[float]]) -> tuple[float, float, float]:
|
||||
return tuple(sum(point[axis] for point in points) / len(points) for axis in range(3))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--out", type=Path, required=True)
|
||||
parser.add_argument("--fixture", type=Path, required=True)
|
||||
parser.add_argument("--layout", type=Path, required=True)
|
||||
parser.add_argument("--headless", type=Path, required=True)
|
||||
parser.add_argument("--renderer", type=Path, required=True)
|
||||
parser.add_argument("--artifact-dir", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
out = args.out.resolve()
|
||||
artifact_dir = args.artifact_dir.resolve()
|
||||
capture_dir = out / "lattice-capture-png"
|
||||
mesh_dir = out / "lattice-meshes"
|
||||
for directory in (capture_dir, mesh_dir, artifact_dir):
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
layout = json.loads(args.layout.read_text(encoding="utf-8"))
|
||||
edges = sorted(layout.get("cavern_edges", []), key=lambda row: row["stable_id"])
|
||||
helix = next((edge for edge in edges
|
||||
if edge["stable_id"] == "lattice/spine/01-helix-ramp-v1"), None)
|
||||
shaft_core = next((room for room in layout.get("cavern_rooms", [])
|
||||
if room["stable_id"] == "lattice/spine/shaft-core-v2"), None)
|
||||
maze_edges = [edge for edge in edges
|
||||
if edge["stable_id"].startswith("lattice/spine/maze-edge-")]
|
||||
if helix is None or shaft_core is None or len(maze_edges) < 4:
|
||||
raise ValueError("layout is missing the resolved open shaft, helix, or maze spine")
|
||||
helix_points = [[value / 1_000_000.0 for value in point]
|
||||
for point in helix["polyline_um"]]
|
||||
core_center = [value / 1_000_000.0 for value in shaft_core["center_um"]]
|
||||
shaft_center = (core_center[0], core_center[1], core_center[2])
|
||||
top = helix_points[0]
|
||||
bottom = helix_points[-1]
|
||||
whole_mesh = mesh_dir / "lattice-whole-view.vfmesh"
|
||||
export_layout = out / "layouts" / "lattice-capture-layout.json"
|
||||
env = os.environ.copy()
|
||||
env["LP_NUM_THREADS"] = "12"
|
||||
env["XDG_CACHE_HOME"] = str(out / "xdg-cache")
|
||||
export_camera = (top[0], top[1], top[2] + 1.5, 0.0, -8.0)
|
||||
run_step("lattice-whole-view-export", [args.headless, args.fixture,
|
||||
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", "--force-generate", "--clip-radius=4",
|
||||
"--max-clip-level=0", "--max-level=0", "--render-distance-chunks=0",
|
||||
"--recipe=lattice", "--lattice-strate=1",
|
||||
f"--camera={fmt(export_camera)}", f"--resolved-layout-output={export_layout}",
|
||||
f"--mesh-export={whole_mesh}"], out / "capture-lattice-export.log",
|
||||
env=env, stamped=True)
|
||||
if not whole_mesh.is_file() or whole_mesh.stat().st_size == 0:
|
||||
raise RuntimeError("whole-lattice export produced no mesh")
|
||||
|
||||
cutaway_mesh = mesh_dir / "lattice-cutaway-y-center.vfmesh"
|
||||
cutaway_triangles = filter_mesh_y_leq(whole_mesh, cutaway_mesh, shaft_center[1])
|
||||
print(f"[L59LatticeCutaway] plane_y_m={shaft_center[1]:.3f} "
|
||||
f"retained_triangles={cutaway_triangles} mesh={cutaway_mesh}", flush=True)
|
||||
|
||||
# Start inside the open shaft, offset toward the first tread so the view
|
||||
# follows the well down while keeping the helical ledges in frame.
|
||||
start_dx = top[0] - shaft_center[0]
|
||||
start_dy = top[1] - shaft_center[1]
|
||||
start_radius = max(1.0e-9, math.hypot(start_dx, start_dy))
|
||||
top_camera = (shaft_center[0] + start_dx / start_radius * 3.5,
|
||||
shaft_center[1] + start_dy / start_radius * 3.5,
|
||||
top[2] + 1.6)
|
||||
top_target = (shaft_center[0], shaft_center[1], bottom[2] + 1.3)
|
||||
eye_index = len(helix_points) // 2
|
||||
eye_floor = helix_points[eye_index]
|
||||
eye_dx = eye_floor[0] - shaft_center[0]
|
||||
eye_dy = eye_floor[1] - shaft_center[1]
|
||||
eye_radius = max(1.0e-9, math.hypot(eye_dx, eye_dy))
|
||||
# Put the eye on the walk line, looking across the open core at the far
|
||||
# wall and the ledges above and below.
|
||||
eye_camera = (eye_floor[0], eye_floor[1], eye_floor[2] + 1.35)
|
||||
eye_target = (shaft_center[0], shaft_center[1], eye_camera[2] + 0.35)
|
||||
maze_edge = next(edge for edge in maze_edges
|
||||
if edge["stable_id"] == "lattice/spine/maze-edge-02-v1")
|
||||
maze_points = [[value / 1_000_000.0 for value in point]
|
||||
for point in maze_edge["polyline_um"]]
|
||||
maze_a, maze_b = maze_points[0], maze_points[-1]
|
||||
maze_t = 0.5
|
||||
maze_floor = tuple(maze_a[axis] + (maze_b[axis] - maze_a[axis]) * maze_t
|
||||
for axis in range(3))
|
||||
maze_horizontal = max(1.0e-9, math.hypot(maze_b[0] - maze_a[0],
|
||||
maze_b[1] - maze_a[1]))
|
||||
maze_camera = (maze_floor[0] - (maze_b[1] - maze_a[1]) / maze_horizontal * 0.2,
|
||||
maze_floor[1] + (maze_b[0] - maze_a[0]) / maze_horizontal * 0.2,
|
||||
maze_floor[2] + 1.4)
|
||||
maze_target = (maze_camera[0] + (maze_b[0] - maze_a[0]) / maze_horizontal * 8.0,
|
||||
maze_camera[1] + (maze_b[1] - maze_a[1]) / maze_horizontal * 8.0,
|
||||
maze_camera[2] + (maze_b[2] - maze_a[2]) / maze_horizontal * 8.0)
|
||||
overview_camera = (shaft_center[0], shaft_center[1] + 12.0, top[2] + 4.0)
|
||||
overview_target = (shaft_center[0], shaft_center[1],
|
||||
(top[2] + bottom[2]) * 0.5 - 2.0)
|
||||
views = (
|
||||
("shaft-top-down", whole_mesh, top_camera, top_target, 22.0, 78.0, None),
|
||||
("ramp-eye-across", whole_mesh, eye_camera, eye_target, 15.0, 100.0, None),
|
||||
("maze-corridor", whole_mesh, maze_camera, maze_target, 8.0, 45.0, None),
|
||||
("shaft-overview", cutaway_mesh, overview_camera, overview_target, 40.0, 80.0, None),
|
||||
)
|
||||
quality = ROOT / "Standalone/Tools/verify/capture_quality.py"
|
||||
for name, render_mesh, camera, target, focus_radius, fov, clip_plane in views:
|
||||
yaw, pitch = camera_angles(camera, target)
|
||||
if name == "ramp-eye-across":
|
||||
yaw, pitch = camera_angles(camera, target)
|
||||
png = capture_dir / f"lattice-{name}.png"
|
||||
image = artifact_dir / f"lattice-{name}.jpg"
|
||||
height_tint = "off" if name == "maze-corridor" else "on"
|
||||
render_args = [args.renderer, render_mesh, png,
|
||||
"--size=1280x720", f"--fov-deg={fov:.1f}",
|
||||
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}",
|
||||
f"--pitch-deg={pitch:.6f}", "--tile-skirts=off", "--shade=readable",
|
||||
"--headlamp=on", f"--height-tint={height_tint}",
|
||||
f"--height-center-m={(top[2] + bottom[2]) * 0.5:.6f}",
|
||||
"--height-range-m=24", "--tile-edges=off",
|
||||
f"--focus-region={fmt((*target, focus_radius))}"]
|
||||
if clip_plane is not None:
|
||||
render_args.append(f"--clip-plane={fmt(clip_plane)}")
|
||||
run_step(f"{name}-render", render_args,
|
||||
out / f"capture-{name}-render.log", env=env, stamped=True)
|
||||
run_step(f"{name}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel",
|
||||
"error", "-y", "-i", png, "-frames:v", "1", "-q:v", "5", image],
|
||||
out / f"capture-{name}-jpeg.log", env=env)
|
||||
quality_output = run_step(f"{name}-quality", [sys.executable, quality, image],
|
||||
out / f"capture-{name}-quality.log", env=env)
|
||||
quality_lines = [line for line in quality_output.splitlines()
|
||||
if line.startswith("[CaptureQuality]")]
|
||||
if not quality_lines or not quality_lines[-1].endswith("status=PASS"):
|
||||
raise RuntimeError(f"capture quality gate failed for {name}")
|
||||
size = image.stat().st_size
|
||||
if size > 300_000:
|
||||
raise RuntimeError(f"capture exceeds 300 KB: {image} ({size} bytes)")
|
||||
print(f"[L59LatticeCapture] view={name} file={image} bytes={size} "
|
||||
f"sha256={sha256(image)} status=PASS", flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as error:
|
||||
print(f"[L59LatticeCapture] status=FAIL error={error}", file=sys.stderr, flush=True)
|
||||
raise SystemExit(1)
|
||||
@@ -0,0 +1,49 @@
|
||||
# Linux59 report
|
||||
|
||||
## Geometry and route
|
||||
|
||||
Resolved shaft: 21.0 m outer envelope; 4.5 m core radius; four-turn ramp at 6.0 m centerline radius, 3.0 m clear width, and 5.0 m pitch. The ramp spans radii 4.5–7.5 m and has 1.0 m floor support. Its inner edge is open to the core. The route keeps a 1.5 m margin from that edge (1.16 m capsule drop clearance). No raised lip was added.
|
||||
|
||||
Maximum route grade is 10.028° at the inner edge; minimum measured headroom is 2.20 m. Arrival-to-departure reachability passes all 883 stations, 44,150 capsule samples, and 4,415 support/headroom samples. The orphan-shaft control is disconnected as expected.
|
||||
|
||||
## Audits and determinism
|
||||
|
||||
The final-field interval oracle checked 2,058 samples in six boxes with zero misses. It proves shaft-core air, returns a mixed result at the core wall, rejects false all-air and all-solid proofs, and blocks the occupied maze tile.
|
||||
|
||||
The whole-surface audit passes with zero flagged triangles and zero clusters. Its scope includes a 0.25 m ramp-edge margin and the 1.0 m underside. The L45 ramp, L46 entrance, and L47 wall-foot controls fail as expected; the plain-native control passes. GCC and Clang camera peers at the shaft center `(11.790, 4.090, -130.630)` match across W1/W8/W12.
|
||||
|
||||
## Speed
|
||||
|
||||
| Measurement | L58 instrumented | L59 final |
|
||||
|---|---:|---:|
|
||||
| First tile | 0.222 s | 0.210 s |
|
||||
| Near ring | 0.364 s | 0.328 s |
|
||||
| Whole view | 1.625 s (1.69 s cited) | 1.605 s |
|
||||
| Moving p50 / p95 | 90.567 / 165.304 ms | 479.544 / 963.764 ms |
|
||||
|
||||
The roughly 1 s whole-view target was not reached; the measured whole view improved only slightly, and moving latency regressed. L59 tile-drain instrumentation recorded 972 stage rows and 28,507 ms summed worker time: 23,332 ms in marching cubes and 717 ms in column work. Marching cubes dominates the measured worker time.
|
||||
|
||||
## Hashes, tiers, and captures
|
||||
|
||||
Source stamp: `389ac77f5d35328a68e4639cac4f7d03ad5ab13b72d4832e89459437cf960ca4`
|
||||
Resolved layout: `resolved-layout-v3-fnv64:ab2f330ada023393`
|
||||
Headless SHA-256: Clang `9fc1ac6c4c5a18b6bbdc0d907d26fcaaa63a49c163a68365f3cc79726db1c474`; GCC `47b37a28a7f132af2a3f91cdd61693fb8ac519e2f23f4064b48bec0b7f8ad964`.
|
||||
|
||||
Fast passed in 174.7 s; medium passed in 1,140.1 s; full passed in 1,206.4 s with its own 7,200 s timeout. Trial count: 15/15. Earlier failed trials exposed ramp-surface spikes and an orphan-control interval false proof; the final surface and interval controls pass.
|
||||
|
||||
All captures are JPEG and under 300 KB:
|
||||
|
||||
- [Shaft top-down](captures/lattice-shaft-top-down.jpg) — 31,500 B — SHA-256 `83e38fce238ddbde84fe3ee13867f5722eac30fe177c44e746eb06b7ae341783`
|
||||
- [Ramp eye across shaft](captures/lattice-ramp-eye-across.jpg) — 24,409 B — SHA-256 `985a16fbd45c986c5bcdd0974e1cc32aac82a06a84b2df98a7b6a358f930cf14`
|
||||
- [Maze corridor](captures/lattice-maze-corridor.jpg) — 15,746 B — SHA-256 `fe72cdd5a3907f8b9071784b08889719593d8839f69a863aa45dc9ac547e771d`
|
||||
- [Shaft overview](captures/lattice-shaft-overview.jpg) — 39,081 B — SHA-256 `6394f0656200f91e77a23aed9c135e2e913dd0053c301921dff24336713c52d2`
|
||||
|
||||
## Orchestrator review (Claude)
|
||||
|
||||
**Parked on `wip/l59-open-shaft`, not merged.** The top-down capture finally reads as an open shaft with a ledge
|
||||
spiralling down the wall, so the concept is right. But the ramp's **inner (open) edge is severely jagged:** shard-like
|
||||
torn scallops along the whole edge (`lattice-ramp-eye-across.jpg`, and the top-down rim). The whole-surface audit
|
||||
passed only because its ROI stops 0.25 m in from the ramp edge: the blind-spot pattern again. Speed moved only
|
||||
1.69 → 1.61 s, at 15 of 15 trials. `assembler/world` keeps L58. Likely cause, to verify: a 1 m-thick ledge with a free
|
||||
edge is a thin feature meshed at 25 cm. The edge needs a bullnose/fillet radius of at least 2 voxels and a thickness
|
||||
the sampler resolves, and the audit ROI must include the edge itself.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -1032,8 +1032,9 @@ namespace
|
||||
constexpr double FloorToSoffit=2.6;
|
||||
constexpr double Pitch=5.0;
|
||||
constexpr int Turns=4;
|
||||
constexpr double RampSupportDepth=1.0;
|
||||
constexpr double FloorBlend=1.9;
|
||||
constexpr double FeatureUnionBlend=0.05;
|
||||
constexpr double FeatureUnionBlend=1.0;
|
||||
constexpr double CorridorSpacing=14.0;
|
||||
constexpr double MazeCellSpan=16.0;
|
||||
constexpr double RouteGradeLimit=13.0;
|
||||
@@ -1053,7 +1054,7 @@ namespace
|
||||
{for(int byte=0;byte<8;++byte){hash^=static_cast<std::uint8_t>(word>>(byte*8));hash*=1099511628211ull;}};
|
||||
addWord(static_cast<std::uint32_t>(seed));
|
||||
addWord(static_cast<std::uint32_t>(latticeStrateIndex));
|
||||
addWord(1);
|
||||
addWord(NativeLatticeFieldRecipeVersion);
|
||||
for(unsigned char value:stableId){hash^=value;hash*=1099511628211ull;}
|
||||
hash^=hash>>30;hash*=0xbf58476d1ce4e5b9ull;
|
||||
hash^=hash>>27;hash*=0x94d049bb133111ebull;
|
||||
@@ -1083,7 +1084,8 @@ namespace
|
||||
edge.boreRadiusMicrometres=1500000;
|
||||
edge.boreHeightMicrometres=static_cast<std::int64_t>(
|
||||
std::llround(FloorToSoffit*1.0e6));
|
||||
edge.floorSupportDepthMicrometres=1000000;
|
||||
edge.floorSupportDepthMicrometres=static_cast<std::int64_t>(
|
||||
std::llround(RampSupportDepth*1.0e6));
|
||||
edge.smoothWidthMicrometres=static_cast<std::int64_t>(std::llround(FloorBlend*1.0e6));
|
||||
edge.unionWidthMicrometres=static_cast<std::int64_t>(
|
||||
std::llround(FeatureUnionBlend*1.0e6));
|
||||
@@ -1121,6 +1123,26 @@ namespace
|
||||
helixEnd[1]+orphanOffset[1],helixEnd[2]});
|
||||
const std::string departureRoomId="lattice/spine/departure-room-v1";
|
||||
addRoom(departureRoomId,{departure[0],departure[1],departureFloor});
|
||||
constexpr double ShaftCoreRadius=CenterlineRadius-ClearWidth*0.5;
|
||||
constexpr double ShaftCoreBottomExtension=2.0;
|
||||
constexpr double ShaftCoreTopExtension=8.0;
|
||||
const double shaftCoreBottom=helixEndFloor-ShaftCoreBottomExtension;
|
||||
const double shaftCoreTop=helixStart[2]+ShaftCoreTopExtension;
|
||||
ResolvedCavernRoomV1 shaftCore;
|
||||
shaftCore.stableId="lattice/spine/shaft-core-v2";
|
||||
shaftCore.strateIndex=latticeStrateIndex;
|
||||
shaftCore.centerMicrometres=micronPoint({
|
||||
helixCenter[0]+orphanOffset[0],helixCenter[1]+orphanOffset[1],
|
||||
(shaftCoreBottom+shaftCoreTop)*0.5});
|
||||
shaftCore.halfExtentsMicrometres={
|
||||
static_cast<std::int64_t>(std::llround(ShaftCoreRadius*1.0e6)),
|
||||
static_cast<std::int64_t>(std::llround(ShaftCoreRadius*1.0e6)),
|
||||
static_cast<std::int64_t>(std::llround((shaftCoreTop-shaftCoreBottom)*0.5e6))};
|
||||
shaftCore.floorMicrometres=static_cast<std::int64_t>(
|
||||
std::llround(shaftCoreBottom*1.0e6));
|
||||
shaftCore.smoothWidthMicrometres=0;
|
||||
shaftCore.unionWidthMicrometres=0;
|
||||
rooms.push_back(std::move(shaftCore));
|
||||
edges.push_back(addEdge("lattice/spine/00-arrival-portal-v1",
|
||||
arrivals.front()->stableId+"/lower-mouth",arrivalRoomId,
|
||||
{{arrival[0],arrival[1],arrivalFloor},helixStart}));
|
||||
@@ -1220,9 +1242,14 @@ namespace
|
||||
std::printf("[LatticeResolvedMouths] arrival=%s side=lower xyz_m=(%.3f,%.3f,%.3f) departure=%s side=upper xyz_m=(%.3f,%.3f,%.3f) binding=actual-resolved status=PASS\n",
|
||||
arrivals.front()->stableId.c_str(),arrival[0],arrival[1],arrival[2],
|
||||
departures.front()->stableId.c_str(),departure[0],departure[1],departure[2]);
|
||||
std::printf("[LatticeSpinePlan] recipe=Core.Lattice.PrimitiveField version=1 strate=%d stable_key=seed,strate,recipe_version,stable_id spine_first=true arrival=%s departure=%s helix_id=lattice/spine/01-helix-ramp-v1 centerline_radius_m=%.3f clear_width_m=%.3f pitch_m=%.3f turns=%d floor_to_soffit_m=%.3f centerline_grade_deg=%.3f inner_edge_grade_deg=%.3f floor_blend_m=%.3f union_envelope_m=%.3f maze_cells=%d dead_ends=2 orphan_control=%s status=PASS\n",
|
||||
std::printf("[LatticeSpinePlan] recipe=Core.Lattice.PrimitiveField version=%u strate=%d stable_key=seed,strate,recipe_version,stable_id spine_first=true arrival=%s departure=%s helix_id=lattice/spine/01-helix-ramp-v1 shaft_core_id=%s shaft_core_radius_m=%.3f centerline_radius_m=%.3f clear_width_m=%.3f pitch_m=%.3f turns=%d inner_edge_margin_m=%.3f capsule_drop_clearance_m=%.3f support_depth_m=%.3f ramp_vertical_clearance_m=%.3f shaft_outer_envelope_m=%.3f maze_headroom_m=%.3f centerline_grade_deg=%.3f inner_edge_grade_deg=%.3f floor_blend_m=%.3f union_envelope_m=%.3f maze_cells=%d dead_ends=2 orphan_control=%s status=PASS\n",
|
||||
NativeLatticeFieldRecipeVersion,
|
||||
latticeStrateIndex,arrivals.front()->stableId.c_str(),departures.front()->stableId.c_str(),
|
||||
CenterlineRadius,ClearWidth,Pitch,Turns,FloorToSoffit,centerlineGrade,innerEdgeGrade,
|
||||
"lattice/spine/shaft-core-v2",ShaftCoreRadius,CenterlineRadius,ClearWidth,Pitch,Turns,
|
||||
CenterlineRadius-ShaftCoreRadius,CenterlineRadius-ShaftCoreRadius-0.34,
|
||||
RampSupportDepth,Pitch-RampSupportDepth,
|
||||
2.0*(CenterlineRadius+ClearWidth*0.5+3.0),
|
||||
FloorToSoffit,centerlineGrade,innerEdgeGrade,
|
||||
FloorBlend,FeatureUnionBlend,routeEdges+1,orphanControl?"enabled":"disabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1452,7 +1452,10 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
std::sort(mazeEdges.begin(),mazeEdges.end(),[](const auto* a,const auto* b)
|
||||
{return a->stableId<b->stableId;});
|
||||
const auto shaftCore=std::find_if(layout.cavernRooms.begin(),layout.cavernRooms.end(),
|
||||
[](const auto& room){return room.stableId=="lattice/spine/shaft-core-v2";});
|
||||
if(!arrivalPortal||!helix||!departurePortal||mazeEdges.size()<4
|
||||
||shaftCore==layout.cavernRooms.end()
|
||||
||layout.cavernRooms.size()<mazeEdges.size()+1)return false;
|
||||
std::vector<const ResolvedCavernEdgeV1*> ordered={arrivalPortal,helix};
|
||||
ordered.insert(ordered.end(),mazeEdges.begin(),mazeEdges.end());
|
||||
@@ -1641,6 +1644,17 @@ namespace StandaloneStreamBenchmark
|
||||
struct FIntervalCheck {const char* name;std::array<double,3> p;};
|
||||
const auto mazeA=metres(mazeEdges.front()->polylineMicrometres.front());
|
||||
const auto mazeB=metres(mazeEdges.front()->polylineMicrometres.back());
|
||||
const auto helixFirst=metres(helix->polylineMicrometres.front());
|
||||
const double helixCenterlineRadius=std::hypot(
|
||||
helixFirst[0]-shaftCore->centerMicrometres[0]/1.0e6,
|
||||
helixFirst[1]-shaftCore->centerMicrometres[1]/1.0e6);
|
||||
const double helixBoreRadius=helix->boreRadiusMicrometres/1.0e6;
|
||||
const double outerWallRadius=helixCenterlineRadius+helixBoreRadius;
|
||||
const double wallScale=outerWallRadius/std::max(1.0e-9,helixCenterlineRadius);
|
||||
const double outerWallX=shaftCore->centerMicrometres[0]/1.0e6
|
||||
+(helixFirst[0]-shaftCore->centerMicrometres[0]/1.0e6)*wallScale;
|
||||
const double outerWallY=shaftCore->centerMicrometres[1]/1.0e6
|
||||
+(helixFirst[1]-shaftCore->centerMicrometres[1]/1.0e6)*wallScale;
|
||||
const std::array<double,3> mazeMid={(mazeA[0]+mazeB[0])*0.5,
|
||||
(mazeA[1]+mazeB[1])*0.5,(mazeA[2]+mazeB[2])*0.5};
|
||||
const double mazeLength=std::hypot(mazeB[0]-mazeA[0],mazeB[1]-mazeA[1]);
|
||||
@@ -1653,17 +1667,23 @@ namespace StandaloneStreamBenchmark
|
||||
for(const auto& edge:layout.cavernEdges)if(edge.strateIndex==lattice->strateIndex)
|
||||
for(const auto& point:edge.polylineMicrometres)
|
||||
{const auto p=metres(point);maxX=std::max(maxX,p[0]);minY=std::min(minY,p[1]);maxY=std::max(maxY,p[1]);}
|
||||
const std::array<FIntervalCheck,4> checks={{{"host-rock",{maxX+8.0,
|
||||
const std::array<FIntervalCheck,6> checks={{{"host-rock",{maxX+8.0,
|
||||
(minY+maxY)*0.5,midZ+1.2}},
|
||||
{"maze-air",{mazeMid[0],mazeMid[1],mazeMid[2]+1.25}},
|
||||
{"maze-wall-foot",{mazeMid[0]+wallX*1.30,mazeMid[1]+wallY*1.30,
|
||||
mazeMid[2]+0.25}},
|
||||
{"helix-air",{helix->polylineMicrometres[helix->polylineMicrometres.size()/2][0]/1.0e6,
|
||||
helix->polylineMicrometres[helix->polylineMicrometres.size()/2][1]/1.0e6,
|
||||
helix->polylineMicrometres[helix->polylineMicrometres.size()/2][2]/1.0e6+1.25}}}};
|
||||
helix->polylineMicrometres[helix->polylineMicrometres.size()/2][2]/1.0e6+1.25}},
|
||||
{"shaft-core-air",{shaftCore->centerMicrometres[0]/1.0e6,
|
||||
shaftCore->centerMicrometres[1]/1.0e6,
|
||||
shaftCore->centerMicrometres[2]/1.0e6}},
|
||||
{"shaft-outer-wall",{outerWallX,outerWallY,
|
||||
helixFirst[2]+1.25}}}};
|
||||
std::uint64_t boxes=0,samples=0,misses=0,allAir=0,allSolid=0,mixed=0;
|
||||
bool falseAllAirRejected=false,falseAllSolidRejected=false,wallMixed=false;
|
||||
bool isTileShapeFreeAware=false,latticeTileFree=false;
|
||||
bool shaftCoreAirProved=false,shaftCoreWallMixed=false;
|
||||
for(const auto& check:checks)
|
||||
{
|
||||
const Vec3i origin{static_cast<int>(std::llround(check.p[0]/vm)),
|
||||
@@ -1684,6 +1704,10 @@ namespace StandaloneStreamBenchmark
|
||||
falseAllAirRejected|=bruteSolid&&verdict!=TileClass::allAir;
|
||||
falseAllSolidRejected|=bruteAir&&verdict!=TileClass::allSolid;
|
||||
if(std::string_view(check.name)=="maze-wall-foot")wallMixed=verdict==TileClass::mixed;
|
||||
if(std::string_view(check.name)=="shaft-core-air")
|
||||
shaftCoreAirProved=verdict==TileClass::allAir&&bruteAir;
|
||||
if(std::string_view(check.name)=="shaft-outer-wall")
|
||||
shaftCoreWallMixed=verdict==TileClass::mixed&&!bruteAir&&!bruteSolid;
|
||||
if(std::string_view(check.name)=="maze-air")
|
||||
{
|
||||
const int32 tileWidth=CoreTileVoxelCount;
|
||||
@@ -1701,9 +1725,9 @@ namespace StandaloneStreamBenchmark
|
||||
(verdict==TileClass::allAir?!bruteAir:
|
||||
verdict==TileClass::allSolid?!bruteSolid:false)?"yes":"no");
|
||||
}
|
||||
const bool intervalOk=boxes>=4&&allSolid>=1&&mixed>=1
|
||||
const bool intervalOk=boxes>=6&&allAir>=1&&allSolid>=1&&mixed>=2
|
||||
&&misses==0&&falseAllAirRejected&&falseAllSolidRejected&&wallMixed
|
||||
&&isTileShapeFreeAware;
|
||||
&&shaftCoreAirProved&&shaftCoreWallMixed&&isTileShapeFreeAware;
|
||||
const bool pathOk=continuous&&capsuleClear&&allSupported&&headroomClear
|
||||
&&maximumGrade<=15.0&&minimumHeadroom>=2.2;
|
||||
std::printf("[LatticeCapsuleReachability] arrival=%s departure=%s edges=%zu path_points=%zu stations=%llu capsule_samples=%llu capsule_failures=%llu first_capsule_failure_m=(%.3f,%.3f,%.3f) support_samples=%llu support_failures=%llu first_support_failure_m=(%.3f,%.3f,%.3f) head_samples=%llu head_failures=%llu first_head_failure_m=(%.3f,%.3f,%.3f) max_grade_deg=%.3f min_headroom_m=%.3f required_headroom_m=2.200 support=every-station reachable=%s status=%s\n",
|
||||
@@ -1721,10 +1745,12 @@ namespace StandaloneStreamBenchmark
|
||||
minimumHeadroom, pathOk?"yes":"no",pathOk?"PASS":"FAIL");
|
||||
std::printf("[LatticeFilletAudit] smooth_max=cubic width_m=%.6f measured_radius_m=%.6f ideal_radius_m=%.6f status=%s\n",
|
||||
width,measuredFillet,idealFillet,measuredFillet>=0.5?"PASS":"FAIL");
|
||||
std::printf("[LatticeIntervalOracle] boxes=%llu all_air=%llu all_solid=%llu mixed=%llu samples=%llu misses=%llu false_all_air=%s false_all_solid=%s wall_foot=%s isTileShapeFree=%s tile_free=%s status=%s\n",
|
||||
std::printf("[LatticeIntervalOracle] boxes=%llu all_air=%llu all_solid=%llu mixed=%llu samples=%llu misses=%llu shaft_core_air=%s shaft_core_wall=%s false_all_air=%s false_all_solid=%s wall_foot=%s isTileShapeFree=%s tile_free=%s status=%s\n",
|
||||
static_cast<unsigned long long>(boxes),static_cast<unsigned long long>(allAir),
|
||||
static_cast<unsigned long long>(allSolid),static_cast<unsigned long long>(mixed),
|
||||
static_cast<unsigned long long>(samples),static_cast<unsigned long long>(misses),
|
||||
shaftCoreAirProved?"PROVED":"FAILED",
|
||||
shaftCoreWallMixed?"mixed":"not-mixed",
|
||||
falseAllAirRejected?"REJECTED":"FAILED",
|
||||
falseAllSolidRejected?"REJECTED":"FAILED",wallMixed?"mixed":"not-mixed",
|
||||
isTileShapeFreeAware?"BLOCKED":"FAILED",latticeTileFree?"yes":"no",
|
||||
|
||||
@@ -1153,7 +1153,8 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
:nativeCaves?"Core.Caves.PrimitiveField"
|
||||
:type==ECaveGeneratorType::SurfaceWorld?"SurfaceWorld.Heightfield2D"
|
||||
:type==ECaveGeneratorType::FloatingIslands?"FloatingIslands.ClusterField":"legacy."+std::string(GeneratorKind(type));
|
||||
s.recipeVersion=(nativeCaves||nativeLattice)?1
|
||||
s.recipeVersion=nativeCaves?1
|
||||
:nativeLattice?VoxelForgeCore::NativeLatticeFieldRecipeVersion
|
||||
:type==ECaveGeneratorType::SurfaceWorld?assembly.recipe.schemaVersion
|
||||
:type==ECaveGeneratorType::FloatingIslands&&!assembly.floatingIslands.empty()
|
||||
?assembly.floatingIslands.front().manifest.recipe.geometryVersion:0;
|
||||
|
||||
@@ -27,7 +27,7 @@ LINUX47 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux47"
|
||||
LINUX53 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux53"
|
||||
LINUX54 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux54"
|
||||
LINUX56 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux56"
|
||||
LINUX58 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux58"
|
||||
LINUX59 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux59"
|
||||
OWNER = ROOT / "Standalone/data/owner_world.json"
|
||||
OUT_DEFAULT = ROOT / "Saved/Linux/l42"
|
||||
|
||||
@@ -672,9 +672,9 @@ def run_native_caves_field_audit(runner: VerificationRunner, binary: Path) -> tu
|
||||
|
||||
|
||||
def build_lattice_fixture(runner: VerificationRunner, binary: Path) -> Path:
|
||||
fixture = OUT / "fixtures" / "lattice-l58.json"
|
||||
builder = LINUX58 / "BuildLatticeFixtureL58.py"
|
||||
runner.run_stage("build-native-lattice-fixture-l58", builder,
|
||||
fixture = OUT / "fixtures" / "lattice-l59.json"
|
||||
builder = LINUX59 / "BuildLatticeFixtureL59.py"
|
||||
runner.run_stage("build-native-lattice-fixture-l59", builder,
|
||||
["--owner", str(OWNER), "--out", str(fixture)], binary=binary,
|
||||
data=OWNER, recipe="lattice", style=None, env=env_for(),
|
||||
script_inputs=[builder, OWNER])
|
||||
@@ -686,19 +686,24 @@ def run_native_lattice_field_audit(runner: VerificationRunner, binary: Path) ->
|
||||
compiler = compiler_name(binary)
|
||||
layout_path = OUT / "layouts" / f"native-lattice-field-{compiler}.json"
|
||||
styles = {"0": 2, "1": 1, "2": 1}
|
||||
inputs = [LINUX58 / "BuildLatticeFixtureL58.py"]
|
||||
inputs = [LINUX59 / "BuildLatticeFixtureL59.py"]
|
||||
row, output = runner.run_cpp(f"native-lattice-field-{compiler}", binary, fixture,
|
||||
"lattice", ["--lattice-strate=1", "--audit-native-lattice",
|
||||
"--max-clip-level=0", "--render-distance-chunks=0"],
|
||||
expected_style=None, expected_strate_styles=styles, workers=12,
|
||||
max_level=0, layout_path=layout_path, script_inputs=inputs)
|
||||
plan = re.search(r"^\[LatticeSpinePlan\] .*recipe=Core\.Lattice\.PrimitiveField version=1 "
|
||||
r"strate=1 .*spine_first=true .*centerline_radius_m=6\.000 clear_width_m=3\.000 "
|
||||
r"pitch_m=5\.000 turns=4 floor_to_soffit_m=2\.600 .*inner_edge_grade_deg=([0-9.]+) "
|
||||
r"floor_blend_m=1\.900 union_envelope_m=0\.050 maze_cells=([0-9]+) dead_ends=2 .*status=PASS$",
|
||||
plan = re.search(r"^\[LatticeSpinePlan\] .*recipe=Core\.Lattice\.PrimitiveField version=2 "
|
||||
r"strate=1 .*spine_first=true .*shaft_core_id=lattice/spine/shaft-core-v2 "
|
||||
r"shaft_core_radius_m=4\.500 centerline_radius_m=6\.000 clear_width_m=3\.000 "
|
||||
r"pitch_m=5\.000 turns=4 inner_edge_margin_m=1\.500 "
|
||||
r"capsule_drop_clearance_m=1\.160 support_depth_m=1\.000 "
|
||||
r"ramp_vertical_clearance_m=4\.000 shaft_outer_envelope_m=21\.000 "
|
||||
r"maze_headroom_m=2\.600 centerline_grade_deg=[0-9.]+ "
|
||||
r"inner_edge_grade_deg=([0-9.]+) floor_blend_m=1\.900 "
|
||||
r"union_envelope_m=1\.000 maze_cells=([0-9]+) dead_ends=2 .*status=PASS$",
|
||||
output, re.M)
|
||||
runner.require(f"native-lattice-field-{compiler}:recipe-and-spine", bool(plan),
|
||||
"native opt-in version-1 lattice, actual mouths first, four-turn ramp and two branches")
|
||||
"native version-2 open well, measured inner margin, supported four-turn ramp and maze branches")
|
||||
fillet = re.search(r"^\[LatticeFilletAudit\].*measured_radius_m=([0-9.]+) .*status=PASS$",
|
||||
output, re.M)
|
||||
runner.require(f"native-lattice-field-{compiler}:measured-fillet",
|
||||
@@ -713,8 +718,9 @@ def run_native_lattice_field_audit(runner: VerificationRunner, binary: Path) ->
|
||||
f"headroom_m={reach.group(2) if reach else 'MISSING'}")
|
||||
runner.require(f"native-lattice-field-{compiler}:interval-oracle-and-shape-free",
|
||||
all_match(output, r"^\[LatticeIntervalOracle\].*misses=0 "
|
||||
r"shaft_core_air=PROVED shaft_core_wall=mixed "
|
||||
r"false_all_air=REJECTED false_all_solid=REJECTED .*isTileShapeFree=BLOCKED .*status=PASS$"),
|
||||
"BVH intervals match brute-force samples, reject both false proofs and block occupied tiles")
|
||||
"interval bounds prove the shaft core air, find the outer wall, reject false proofs and block occupied tiles")
|
||||
|
||||
orphan_name = f"native-lattice-orphan-control-{compiler}"
|
||||
orphan_layout = OUT / "layouts" / f"{orphan_name}.json"
|
||||
@@ -735,6 +741,7 @@ def run_native_lattice_field_audit(runner: VerificationRunner, binary: Path) ->
|
||||
if row.get("strate_index") == 1]
|
||||
graph_ok = (layout.get("cavern_graph_vocabulary_version") == 1
|
||||
and len(edges) >= 9 and len(rooms) >= 8
|
||||
and any(room.get("stable_id") == "lattice/spine/shaft-core-v2" for room in rooms)
|
||||
and len(lattice_rows) == 1
|
||||
and lattice_rows[0].get("generator_kind") == "Maze"
|
||||
and lattice_rows[0].get("recipe_id") == "Core.Lattice.PrimitiveField"
|
||||
@@ -759,8 +766,10 @@ def lattice_camera_positions(layout_path: Path, count: int = 4) -> list[tuple[fl
|
||||
edges = sorted(layout.get("cavern_edges", []), key=lambda edge: edge["stable_id"])
|
||||
helix = next((edge for edge in edges
|
||||
if edge["stable_id"] == "lattice/spine/01-helix-ramp-v1"), None)
|
||||
if layout.get("cavern_graph_vocabulary_version") != 1 or helix is None:
|
||||
raise VerificationFailure(f"missing resolved lattice helix in {layout_path}")
|
||||
core = next((room for room in layout.get("cavern_rooms", [])
|
||||
if room["stable_id"] == "lattice/spine/shaft-core-v2"), None)
|
||||
if layout.get("cavern_graph_vocabulary_version") != 1 or helix is None or core is None:
|
||||
raise VerificationFailure(f"missing resolved open shaft or helix in {layout_path}")
|
||||
helix_points = [[value / 1_000_000.0 for value in point]
|
||||
for point in helix.get("polyline_um", [])]
|
||||
if len(helix_points) < 4:
|
||||
@@ -773,11 +782,8 @@ def lattice_camera_positions(layout_path: Path, count: int = 4) -> list[tuple[fl
|
||||
point = helix_points[index]
|
||||
tangent = [helix_points[index + 1][axis] - helix_points[index - 1][axis]
|
||||
for axis in range(3)]
|
||||
lateral = rng.between(-0.35, 0.35)
|
||||
horizontal = max(1.0e-9, math.hypot(tangent[0], tangent[1]))
|
||||
x = point[0] - tangent[1] / horizontal * lateral
|
||||
y = point[1] + tangent[0] / horizontal * lateral
|
||||
z = point[2] + rng.between(1.35, 1.55)
|
||||
center = [value / 1_000_000.0 for value in core["center_um"]]
|
||||
x, y, z = center
|
||||
else:
|
||||
route_edges = [edge for edge in edges if edge["stable_id"].startswith(
|
||||
"lattice/spine/maze-edge-")]
|
||||
@@ -830,7 +836,7 @@ def run_lattice_determinism(runner: VerificationRunner, binary: Path,
|
||||
peer_layouts: dict[int, str] = {}
|
||||
key = f"p{camera_index:02d}"
|
||||
current[key] = {"camera_m": [round(value, 6) for value in camera[:3]],
|
||||
"inside_helix": camera_index == 0}
|
||||
"inside_shaft_core": camera_index == 0}
|
||||
for workers in (1, 8, 12):
|
||||
name = (f"lattice-camera-{compiler}-s{LATTICE_CAMERA_SWEEP_SEED:06x}-"
|
||||
f"p{camera_index:02d}-w{workers}")
|
||||
@@ -841,7 +847,7 @@ def run_lattice_determinism(runner: VerificationRunner, binary: Path,
|
||||
"--render-distance-chunks=0"], expected_style=None,
|
||||
expected_strate_styles=styles, workers=workers, max_level=0,
|
||||
layout_path=run_layout,
|
||||
script_inputs=[LINUX58 / "BuildLatticeFixtureL58.py"])
|
||||
script_inputs=[LINUX59 / "BuildLatticeFixtureL59.py"])
|
||||
tiles = parse_tile_hashes(output)
|
||||
whole = re.search(r"^\[PipelineWholeViewHash\] tiles=(\d+)", output, re.M)
|
||||
expected_rows = int(whole.group(1)) if whole else -1
|
||||
@@ -871,17 +877,34 @@ def run_lattice_determinism(runner: VerificationRunner, binary: Path,
|
||||
def run_lattice_performance(runner: VerificationRunner, binary: Path,
|
||||
fixture: Path, layout_path: Path) -> None:
|
||||
compiler = compiler_name(binary)
|
||||
camera = lattice_camera_positions(layout_path, count=1)[0]
|
||||
layout = json.loads(layout_path.read_text(encoding="utf-8"))
|
||||
helix = next(edge for edge in layout.get("cavern_edges", [])
|
||||
if edge["stable_id"] == "lattice/spine/01-helix-ramp-v1")
|
||||
helix_points = [[value / 1_000_000.0 for value in point]
|
||||
for point in helix.get("polyline_um", [])]
|
||||
index = len(helix_points) // 2
|
||||
point = helix_points[index]
|
||||
tangent = [helix_points[index + 1][axis] - helix_points[index - 1][axis]
|
||||
for axis in range(3)]
|
||||
horizontal = max(1.0e-9, math.hypot(tangent[0], tangent[1]))
|
||||
rng = _SplitMix64(LATTICE_CAMERA_SWEEP_SEED)
|
||||
lateral = rng.between(-0.35, 0.35)
|
||||
x = point[0] - tangent[1] / horizontal * lateral
|
||||
y = point[1] + tangent[0] / horizontal * lateral
|
||||
z = point[2] + rng.between(1.35, 1.55)
|
||||
yaw = math.degrees(math.atan2(tangent[0], tangent[1])) + rng.between(-14.0, 14.0)
|
||||
pitch = rng.between(-6.0, 6.0)
|
||||
camera = (x, y, z, yaw, pitch)
|
||||
x, y, z, yaw, pitch = camera
|
||||
camera_flag = f"--camera={x:.6f},{y:.6f},{z:.6f},{yaw:.3f},{pitch:.3f}"
|
||||
name = f"lattice-speed-{compiler}-w12"
|
||||
row, output = runner.run_cpp(name, binary, fixture, "lattice",
|
||||
["--lattice-strate=1", camera_flag, "--clip-radius=4", "--max-clip-level=0",
|
||||
"--max-level=0", "--render-distance-chunks=0", "--camera-step=24,0,0",
|
||||
"--region-crossing"], expected_style=None,
|
||||
"--region-crossing", "--instrument-tile-drain"], expected_style=None,
|
||||
expected_strate_styles={"0": 2, "1": 1, "2": 1}, workers=12,
|
||||
max_level=0, layout_path=OUT / "layouts" / f"{name}.json",
|
||||
script_inputs=[LINUX58 / "BuildLatticeFixtureL58.py"])
|
||||
script_inputs=[LINUX59 / "BuildLatticeFixtureL59.py"])
|
||||
stream = re.search(r"^\[StreamPipeline\] .*first_tile_ready=([0-9.]+)s "
|
||||
r".*neighborhood_ready=([0-9.]+)s .*whole_view_ready=([0-9.]+)s .*cache_hits=0",
|
||||
output, re.M)
|
||||
@@ -895,6 +918,20 @@ def run_lattice_performance(runner: VerificationRunner, binary: Path,
|
||||
f"whole_view_s={stream.group(3) if stream else 'MISSING'} "
|
||||
f"moving_p50_ms={moving.group(2) if moving else 'MISSING'} "
|
||||
f"moving_p95_ms={moving.group(3) if moving else 'MISSING'}")
|
||||
stage_rows = re.findall(r"^\[CpuTileStages\] (.*)$", output, re.M)
|
||||
def sum_stage(field: str) -> float:
|
||||
values = [float(value) for value in re.findall(
|
||||
rf"(?:^| ){re.escape(field)}=([0-9.]+)(?: |$)", "\n".join(stage_rows))]
|
||||
return sum(values)
|
||||
stage_summary = {field: sum_stage(field) for field in (
|
||||
"worker_ms", "classify_ms", "column_exclusive_ms", "mc_ms",
|
||||
"mc_grid_ms", "mc_cell_ms", "mc_gradient_ms", "mc_interpolation_ms",
|
||||
"mc_dedup_ms", "mc_skirt_ms", "mc_other_ms", "mesh_assembly_ms")}
|
||||
runner.require(f"{name}:tile-drain-instrumentation",
|
||||
bool(stage_rows) and stage_summary["worker_ms"] > 0.0,
|
||||
f"tile_stage_rows={len(stage_rows)} worker_sum_ms={stage_summary['worker_ms']:.3f} "
|
||||
f"mc_ms={stage_summary['mc_ms']:.3f} "
|
||||
f"column_ms={stage_summary['column_exclusive_ms']:.3f}")
|
||||
if stream and moving:
|
||||
row["speed_metrics"] = {
|
||||
"cold_first_tile_seconds": float(stream.group(1)),
|
||||
@@ -905,6 +942,8 @@ def run_lattice_performance(runner: VerificationRunner, binary: Path,
|
||||
"moving_p95_ms": float(moving.group(3)),
|
||||
"workers": 12, "camera_m": [round(value, 6) for value in camera[:3]],
|
||||
}
|
||||
row["tile_drain_instrumentation"] = {
|
||||
"stage_rows": len(stage_rows), "aggregate_ms": stage_summary}
|
||||
|
||||
|
||||
def cave_camera_positions(layout_path: Path, count: int = 4) -> list[tuple[float, float, float, float, float]]:
|
||||
@@ -1103,25 +1142,25 @@ def run_caves_surface_evidence(runner: VerificationRunner, fixture: Path,
|
||||
|
||||
def run_lattice_surface_evidence(runner: VerificationRunner, fixture: Path,
|
||||
layout_path: Path) -> None:
|
||||
capture_script = LINUX58 / "CaptureNativeLatticeL58.py"
|
||||
artifact_dir = LINUX58 / "captures"
|
||||
_, capture_output = runner.run_stage("capture-native-lattice-l58", capture_script,
|
||||
capture_script = LINUX59 / "CaptureNativeLatticeL59.py"
|
||||
artifact_dir = LINUX59 / "captures"
|
||||
_, capture_output = runner.run_stage("capture-native-lattice-l59", capture_script,
|
||||
["--out", str(OUT), "--fixture", str(fixture), "--layout", str(layout_path),
|
||||
"--headless", str(CLANG), "--renderer", str(RENDERER),
|
||||
"--artifact-dir", str(artifact_dir)], binary=CLANG, data=fixture,
|
||||
recipe="lattice", style=2, env=env_for(capture=True),
|
||||
script_inputs=[capture_script, LINUX54 / "CaptureNativeCavesL54.py",
|
||||
VERIFY_DIR / "capture_quality.py"])
|
||||
capture_rows = re.findall(r"^\[L58LatticeCapture\] view=([^ ]+) file=(\S+) "
|
||||
capture_rows = re.findall(r"^\[L59LatticeCapture\] view=([^ ]+) file=(\S+) "
|
||||
r"bytes=(\d+) sha256=([0-9a-f]{64}) status=PASS$", capture_output, re.M)
|
||||
expected_views = {"helix-top-down", "ramp-eye-next-turn", "maze-eye-level",
|
||||
"cutaway-overview"}
|
||||
runner.require("capture-native-lattice-l58:coverage-and-size",
|
||||
expected_views = {"shaft-top-down", "ramp-eye-across", "maze-corridor",
|
||||
"shaft-overview"}
|
||||
runner.require("capture-native-lattice-l59:coverage-and-size",
|
||||
{row[0] for row in capture_rows} == expected_views and len(capture_rows) == 4
|
||||
and all(int(row[2]) <= 300_000 for row in capture_rows),
|
||||
f"views={[(row[0], int(row[2])) for row in capture_rows]}")
|
||||
|
||||
surface_script = LINUX58 / "AuditLatticeSurfaceL58.py"
|
||||
surface_script = LINUX59 / "AuditLatticeSurfaceL59.py"
|
||||
audit_layout = ROOT / "Saved/Linux/l48/layouts/landmark-reachability-clang.json"
|
||||
candidate_mesh = OUT / "lattice-meshes" / "lattice-whole-view.vfmesh"
|
||||
controls = (
|
||||
@@ -1130,26 +1169,27 @@ def run_lattice_surface_evidence(runner: VerificationRunner, fixture: Path,
|
||||
("L47-wall-foot-control", ROOT / "Saved/Linux/l47/landmark-l47.vfmesh", "fail"),
|
||||
("plain-native-control", ROOT / "Saved/Linux/l48/control-plain-native.vfmesh", "pass"),
|
||||
)
|
||||
args = ["--json", str(OUT / "whole-surface-lattice-l58.json"),
|
||||
"--svg-dir", str(artifact_dir), "--case", "lattice-whole",
|
||||
svg_dir = OUT / "surface-audit-svg"
|
||||
args = ["--json", str(OUT / "whole-surface-lattice-l59.json"),
|
||||
"--svg-dir", str(svg_dir), "--case", "lattice-whole",
|
||||
str(candidate_mesh), str(layout_path), "measure", "whole"]
|
||||
for label, mesh, expectation in controls:
|
||||
args.extend(("--case", label, str(mesh), str(audit_layout), expectation, "whole"))
|
||||
_, audit_output = runner.run_stage("whole-surface-lattice-l58", surface_script,
|
||||
_, audit_output = runner.run_stage("whole-surface-lattice-l59", surface_script,
|
||||
args, binary=CLANG, data=fixture, recipe="lattice", style=2,
|
||||
layout_path=layout_path, env=env_for(),
|
||||
script_inputs=[surface_script, LINUX54 / "AuditWholeSurfaceL54.py",
|
||||
candidate_mesh, layout_path, audit_layout, *(item[1] for item in controls)])
|
||||
candidate = re.search(r"^\[WholeSurfaceAudit\] label=lattice-whole .*"
|
||||
r"observed=(PASS|FAIL) expected=MEASURE status=PASS$", audit_output, re.M)
|
||||
runner.require("whole-surface-lattice-l58:helix-and-maze",
|
||||
runner.require("whole-surface-lattice-l59:shaft-lip-wall-foot-and-underside",
|
||||
bool(candidate and candidate.group(1) == "PASS"),
|
||||
f"observed={candidate.group(1) if candidate else 'MISSING'} over helix ramp edges and maze wall feet")
|
||||
for label, _mesh, expectation in controls:
|
||||
match = re.search(rf"^\[WholeSurfaceAudit\] label={re.escape(label)} .*"
|
||||
rf"observed=(PASS|FAIL) expected={expectation.upper()} status=PASS$",
|
||||
audit_output, re.M)
|
||||
runner.require(f"whole-surface-lattice-l58:{label}", bool(match),
|
||||
runner.require(f"whole-surface-lattice-l59:{label}", bool(match),
|
||||
f"control_expectation={expectation} observed={match.group(1) if match else 'MISSING'}")
|
||||
|
||||
|
||||
@@ -2227,7 +2267,7 @@ def run_gcc_supplement(runner: VerificationRunner) -> None:
|
||||
if not fixture.is_file() or not layout_path.is_file():
|
||||
fixture, layout_path = run_native_caves_field_audit(runner, CLANG)
|
||||
run_caves_determinism(runner, GCC, fixture, layout_path, compare_reference=True)
|
||||
lattice_fixture = OUT / "fixtures" / "lattice-l58.json"
|
||||
lattice_fixture = OUT / "fixtures" / "lattice-l59.json"
|
||||
lattice_layout = OUT / "layouts" / "native-lattice-field-clang.json"
|
||||
if not lattice_fixture.is_file() or not lattice_layout.is_file():
|
||||
lattice_fixture, lattice_layout = run_native_lattice_field_audit(runner, CLANG)
|
||||
|
||||
Reference in new issue
Block a user