wip(assembler): M1 round 4 PARTIAL -- stopped at owner's request (GPU coil whine at night)
Unverified mid-round state (cliff plates / horizon slab / routes in progress). Resume from here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
2c20f9eaea
commit
20c6d86f0e
11 files changed
+839
-47
No files matched your search
+9
-1
@@ -1165,7 +1165,7 @@ choices for the next implementation pass:
|
||||
| TunnelNetwork | Keep the native primitive representation; add missing density operators as measured. | Existing rooms, tunnels, and passage chains work, but the 16-tile probe still changed 2.17% of signs and had a 1.01 m mean sampled surface distance. |
|
||||
| FlatPlain and CrystalChamber | Native slab operator using the already copied slab parameters. | Floor, ceiling, columns, and crystal ceiling are a distinct low-cost structure; generic cave primitives do not express them. |
|
||||
| Maze | Native deterministic cell graph and corridor field using FMazeGenerationParams. | The legacy topology is seed-stable and compact; current generic caves changed 13.34% of signs and had a 5.92 m maximum sampled surface distance. |
|
||||
| SurfaceWorld | Native Core heightfield long term; retain the exact legacy density bridge temporarily. | The bridge gives exact tested density hashes but runs near owner-field cost and does not add render-side water or biome materials. |
|
||||
| SurfaceWorld | Keep the exact bridge as the Unreal runtime path until the standalone native evaluator is integrated and owner-reviewed. | The native fixed-scale heightfield, route plan and field sweep now exist in `Standalone/Core`; the plugin season/hash path and in-game review remain open. The bridge still lacks render-side water and biome materials. |
|
||||
| VerticalShafts | Native deterministic shaft/ledge field; use legacy fallback only as a short compatibility step. | Generic cave shapes produced no matching surface in the probe and failed the passage-centerline audit. |
|
||||
| FloatingIslands | Native island SDF with deterministic placement and smooth unions. | The current probe changed 95.75% of signs; an island layout is sparse and does not map well to room/tunnel primitives. |
|
||||
| Underwater | Reuse native TunnelNetwork geometry and add an explicit water render payload. | The density field can reuse cave geometry; water material/plane is separate from signed density and remains absent. |
|
||||
@@ -1178,6 +1178,14 @@ worker path explicit and avoids charging every sample for a second field impleme
|
||||
continues to verify the opt-in Unreal route and its 25 cm Core scale; it does not certify the missing
|
||||
archetypes.
|
||||
|
||||
**Standalone SurfaceWorld M1 progress (2026-10-09):** `WorldAssemblyManifest` evaluator 4 now
|
||||
serializes a finite-strate surface route with stable waypoint IDs, a 0.34 m × 1.76 m player capsule,
|
||||
a 15° grade limit and underfoot support constraints. The final composed field must pass a capsule
|
||||
sweep before a route candidate is accepted. Cliff shelves carry a two-Core-voxel minimum thickness.
|
||||
The route law applies to generation and automatic changes; player carve/fill edits remain unrestricted.
|
||||
This is standalone Core evidence; it is not yet connected to the season asset/hash path or reviewed in
|
||||
the owner's game.
|
||||
|
||||
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||
|
||||
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
||||
|
||||
@@ -827,6 +827,13 @@ the module and passed `CheckCoreSymbols.ps1` against the actual shim objects. Se
|
||||
[`Standalone/UNREAL_INTEGRATION_REPORT.md`](Standalone/UNREAL_INTEGRATION_REPORT.md) for build and
|
||||
headless evidence and the run ledger.
|
||||
|
||||
World assembler field types and `AuditHeightfieldBounds`, `AuditHeightfieldShelfThickness`, and
|
||||
`AuditHeightfieldRoute` are declared in
|
||||
[`Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`](Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h)
|
||||
and implemented in [`Standalone/Core/src/WorldAssemblerInternal.h`](Standalone/Core/src/WorldAssemblerInternal.h).
|
||||
The standalone SurfaceWorld plan composer and capture/audit entry point live in
|
||||
[`Standalone/Source/StandaloneViewer.cpp`](Standalone/Source/StandaloneViewer.cpp).
|
||||
|
||||
`RunStandaloneWorldExport` in `VoxelForgeExploreCommandlet.cpp` serializes owner assets on the game
|
||||
thread to schema-v1 JSON. The adapter parses it through `ResolvedWorldSnapshot::FromJson` and keeps
|
||||
UObjects off Core workers. The runtime switch is `AVoxelWorld::bUseVoxelForgeCore` (default false),
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$OutputName = 'VoxelForgeCoreStandalone.exe',
|
||||
[ValidateSet('All','Core')][string]$Target = 'All'
|
||||
[ValidateSet('All','Core')][string]$Target = 'All',
|
||||
[ValidateRange(1,16)][int]$CompileWorkers = 16,
|
||||
[ValidateSet('Normal','BelowNormal')][string]$BuildPriority = 'Normal'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$BuildHostProcess = Get-Process -Id $PID
|
||||
$BuildHostProcess.PriorityClass = 'BelowNormal'
|
||||
$BuildHostProcess.ProcessorAffinity = [IntPtr]0xFF00
|
||||
$BuildHostProcess.PriorityClass = $BuildPriority
|
||||
$BuildHostProcess.ProcessorAffinity = [IntPtr]0xFFFF
|
||||
$Root = [IO.Path]::GetFullPath($PSScriptRoot)
|
||||
$BuildDir = Join-Path $Root 'Build\VoxelForgeCore'
|
||||
$BinDir = Join-Path $Root 'bin'
|
||||
@@ -72,7 +74,7 @@ $IncludeArgs = @(
|
||||
('/I"{0}"' -f (Join-Path $Root 'Source\VoxelForge\Public')),
|
||||
('/I"{0}"' -f (Join-Path $Root 'Source\VoxelForge\Private'))
|
||||
)
|
||||
$CommonFlags = @('/nologo','/std:c++20','/O2','/arch:AVX2','/fp:precise','/EHsc','/MP8','/D_CRT_SECURE_NO_WARNINGS') + $IncludeArgs
|
||||
$CommonFlags = @('/nologo','/std:c++20','/O2','/arch:AVX2','/fp:precise','/EHsc',("/MP{0}" -f $CompileWorkers),'/D_CRT_SECURE_NO_WARNINGS') + $IncludeArgs
|
||||
$CoreRsp = Join-Path $BuildDir 'core-compile.rsp'
|
||||
$CoreRspLines = $CommonFlags + @('/c') + @($CompileSources | ForEach-Object { '"{0}"' -f $_ })
|
||||
[IO.File]::WriteAllLines($CoreRsp,$CoreRspLines,[Text.UTF8Encoding]::new($false))
|
||||
@@ -89,6 +91,7 @@ if ($Target -eq 'All') {
|
||||
if ($CoreSourceNames[$Index].Replace('\','/') -eq 'Core/src/VoxelForgeCore.cpp') { continue }
|
||||
$LegacySources += $CoreSources[$Index]
|
||||
}
|
||||
$LegacySources += (Join-Path $Root 'Source\GpuFarViewerLinkStubs.cpp')
|
||||
$LegacyObjects = @($LegacySources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) })
|
||||
$LegacyRspLines = $CommonFlags + @('/c') + @($LegacySources | ForEach-Object { '"{0}"' -f $_ })
|
||||
[IO.File]::WriteAllLines($LegacyRsp,$LegacyRspLines,[Text.UTF8Encoding]::new($false))
|
||||
@@ -104,8 +107,8 @@ $ViewerRspLines = $CommonFlags + @('/Fe"{0}"' -f $OutputPath) + $LegacyObjects +
|
||||
|
||||
Push-Location $BuildDir
|
||||
try {
|
||||
# One cl process with /MP8 bounds compiler work to eight processes; MSVC, lib.exe, and the
|
||||
# viewer link inherit BelowNormal priority and the 0xFF00 process affinity from this host.
|
||||
# MSVC's /MP count and the build host's Normal-priority 16-CPU mask are configurable for
|
||||
# isolated BuildHost work; the viewer process uses its own run-time worker setting.
|
||||
$CommandLine = 'call "{0}" >nul && cl.exe @"{1}" && lib.exe @"{2}"' -f $VcVars,$CoreRsp,$LibRsp
|
||||
& $env:ComSpec /d /s /c $CommandLine
|
||||
if ($LASTEXITCODE -eq -1073741502) { throw 'A build process exited with 0xC0000142; stopping as required.' }
|
||||
|
||||
+26
-10
@@ -79,14 +79,15 @@ Pass `analyticOnly = false` to retain the reference packed-page compiler. Packed
|
||||
parent-cell coordinates and an 8-voxel apron; each covers a 128-voxel cube or one larger coarse
|
||||
tile. This path remains available for parity checks and workloads that need the packed lattice.
|
||||
|
||||
## World assembler manifest v1 (prototype; evaluator v1 and v2)
|
||||
## World assembler manifest v1 (evaluator v1–v4)
|
||||
|
||||
`ResolvedWorldSnapshot::FromJson` accepts an optional `world_assembly` object alongside the owner
|
||||
snapshot. The object is the shipped-plan payload shape: a fixed-scale recipe and finite-strate plan
|
||||
with hashes, no generated mesh or voxel samples. The current evaluator-v2 review payload is
|
||||
[`round2/world_assembly.json`](../Research/WorldAssemblerM1/round2/world_assembly.json); the
|
||||
evaluator-v1 sample remains at [`round1-manifest.json`](../Research/WorldAssemblerM1/round1-manifest.json).
|
||||
Round 4 still needs to connect this payload to the season asset/hash path.
|
||||
with hashes, no generated mesh or voxel samples. The current review payload is written to
|
||||
[`round4/world_assembly.json`](../Research/WorldAssemblerM1/round4/world_assembly.json).
|
||||
Evaluator 1–3 payloads remain readable with their original hashes and field semantics. Evaluator 4
|
||||
adds a minimum two-voxel shelf thickness and a versioned route support contract; it requires at
|
||||
least two route waypoints.
|
||||
|
||||
Every coordinate and scalar uses signed integer units of 1/256 voxel (`units_per_voxel: 256`),
|
||||
independent of the plugin's 25 cm Core voxel and of tile size. The schema version is 1; evaluator
|
||||
@@ -96,8 +97,10 @@ order and hashed as 4- or 8-byte little-endian words with FNV-1a 64. Hashes are
|
||||
lowercase hexadecimal characters; JSON key order and whitespace do not affect them. The root,
|
||||
recipe, plan and each stratum carry a content hash. The recipe stores its seed, warped biome-map
|
||||
settings and one to eight biome profiles; the plan stores finite-strate extents, cap settings,
|
||||
stable-ID features, fixed-season water and route points. Bounds, feature count and route count are
|
||||
validated before a manifest is accepted.
|
||||
stable-ID features, fixed-season water, route points and (for evaluator 4) player-capsule, grade,
|
||||
support and sweep-spacing constraints. Route points are fixed-scale capsule-center locations with
|
||||
stable IDs and a protected automatic-change corridor radius. Bounds, feature count, route count, route
|
||||
IDs, corridor widths and finite-strate capsule extents are validated before a manifest is accepted.
|
||||
|
||||
Evaluator 2 adds climate-based biome assignment, output-space blending of both ground and cap
|
||||
profiles, per-biome relief and height warp, beach width, cliff limits, and optional per-biome cap
|
||||
@@ -120,8 +123,11 @@ neighbor biome IDs, Q16 blend weight and signed density. Evaluator 2 also return
|
||||
surface and water-fill sample values. Negative density is solid. Its contract reads `biome` and
|
||||
writes `height`, `rock`, `water` and `waterSurface`; route and `surfaceTags` remain declared channels
|
||||
for later blocks. The detail envelope reports maximum absolute displacement and minimum feature
|
||||
size. Influence is global in XY and limited to the finite stratum's Z extent, except for the
|
||||
explicitly bounded evaluator-v2 feature boxes.
|
||||
size. Evaluator 4 defines the shelf's minimum solid thickness as two Core voxels (0.5 m) and applies
|
||||
its feather to the horizontal footprint so it cannot thin the roof-to-floor section.
|
||||
`AuditHeightfieldShelfThickness` samples shelf sections against the pre-feature field and includes
|
||||
the previous sub-voxel taper as a negative control. Influence is global in XY and limited to the
|
||||
finite stratum's Z extent, except for the explicitly bounded feature boxes.
|
||||
|
||||
`interval(minXQ, minYQ, minZQ, maxXQ, maxYQ, maxZQ)` returns a conservative density range or
|
||||
`known == false`; callers must keep evaluating when it is unknown. The current heightfield range
|
||||
@@ -131,9 +137,19 @@ negative-coordinate and overhang boxes, and includes a deliberately false one-si
|
||||
negative control. A pass means no sampled point escaped and the negative control was rejected; it
|
||||
is an audit of this block implementation, not a proof for future blocks.
|
||||
|
||||
`AuditHeightfieldRoute` sweeps the recorded route through the final composed field using the
|
||||
versioned player capsule (0.34 m radius, 1.76 m height), checks a 15-degree maximum grade, samples
|
||||
capsule clearance at no more than half-voxel intervals, and probes support below five foot locations
|
||||
at each swept center. Automatic changes must be composed first and pass this audit before the plan is
|
||||
accepted. Player carve/fill edits remain unrestricted. This validates analytic field collision and
|
||||
support; it does not cook an Unreal physics mesh.
|
||||
|
||||
## Queries and tiles
|
||||
|
||||
`densityAt(position)` returns signed density; negative is solid and nonnegative is air.
|
||||
`densityAt(position)` returns signed density; negative is solid and nonnegative is air. Server and
|
||||
authority code can compile/query a `World` and run `AuditHeightfieldRoute` without calling
|
||||
`generateTile`; generated render meshes are not part of this query path. Unreal physics collision
|
||||
cooking remains the adapter's separate readiness step.
|
||||
`isSolidAt` wraps that sign test. `isSolidAlongSegment(start, end, spacing)` samples evenly along
|
||||
the segment with no interval wider than `spacing` voxels; it returns true if any sample is solid.
|
||||
`classifyBox(origin, step, cells)` returns `allSolid`, `allAir` or `mixed` for mesher tile
|
||||
|
||||
@@ -82,6 +82,8 @@ namespace VoxelForgeCore
|
||||
double meshingMilliseconds = 0.0;
|
||||
bool tileWasClassified = false;
|
||||
bool meshingSkipped = false;
|
||||
bool featureRefinedResolution = false;
|
||||
std::uint64_t farCapTrianglesSuppressed = 0;
|
||||
};
|
||||
|
||||
struct TileMesh
|
||||
@@ -126,7 +128,7 @@ namespace VoxelForgeCore
|
||||
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
|
||||
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
||||
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1;
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 3;
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 4;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
@@ -193,11 +195,26 @@ namespace VoxelForgeCore
|
||||
|
||||
struct WorldAssemblyRoutePointV1
|
||||
{
|
||||
// Stable ID and fixed-scale capsule-center waypoint. Radius is the protected
|
||||
// automatic-change corridor around this centerline point.
|
||||
std::uint64_t stableFeatureId = 0;
|
||||
std::array<std::int64_t, 3> positionSubvoxels{};
|
||||
std::int64_t radiusSubvoxels = 0;
|
||||
};
|
||||
|
||||
struct WorldAssemblyRouteConstraintsV1
|
||||
{
|
||||
std::uint32_t version = 0;
|
||||
std::int64_t capsuleRadiusSubvoxels = 0;
|
||||
std::int64_t capsuleHeightSubvoxels = 0;
|
||||
std::int64_t maximumGradeQ16 = 0;
|
||||
std::int64_t sweepSpacingSubvoxels = 0;
|
||||
std::int64_t supportProbeRadiusSubvoxels = 0;
|
||||
std::int64_t supportSearchDepthSubvoxels = 0;
|
||||
std::int64_t minimumClearanceSubvoxels = 0;
|
||||
std::uint32_t minimumSupportProbes = 0;
|
||||
};
|
||||
|
||||
struct WorldAssemblyFeatureV1
|
||||
{
|
||||
// Stable numeric type IDs are part of the plan contract: 1 is a heightfield cliff plane,
|
||||
@@ -228,6 +245,7 @@ namespace VoxelForgeCore
|
||||
std::int64_t waterSurfaceSubvoxels = 0;
|
||||
bool waterEnabled = false;
|
||||
std::vector<WorldAssemblyFeatureV1> features;
|
||||
WorldAssemblyRouteConstraintsV1 routeConstraints;
|
||||
std::vector<WorldAssemblyRoutePointV1> route;
|
||||
std::uint64_t contentHash = 0;
|
||||
};
|
||||
@@ -357,10 +375,50 @@ namespace VoxelForgeCore
|
||||
bool passed = false;
|
||||
};
|
||||
|
||||
struct WorldAssemblyShelfThicknessAuditResult
|
||||
{
|
||||
std::uint64_t featuresTested = 0;
|
||||
std::uint64_t columnsTested = 0;
|
||||
std::uint64_t activeColumns = 0;
|
||||
std::uint64_t negativeRuns = 0;
|
||||
std::uint64_t subVoxelColumns = 0;
|
||||
std::uint64_t subVoxelRuns = 0;
|
||||
std::uint64_t legacySubVoxelColumns = 0;
|
||||
std::uint64_t legacySubVoxelRuns = 0;
|
||||
std::int64_t minimumObservedThicknessSubvoxels = 0;
|
||||
std::int64_t minimumThicknessColumnXSubvoxels = 0;
|
||||
std::int64_t minimumThicknessColumnYSubvoxels = 0;
|
||||
std::int64_t minimumThicknessGroundSubvoxels = 0;
|
||||
std::int64_t minimumThicknessBottomSubvoxels = 0;
|
||||
std::int64_t minimumThicknessTopSubvoxels = 0;
|
||||
bool negativeControlDetected = false;
|
||||
bool passed = false;
|
||||
};
|
||||
|
||||
struct WorldAssemblyRouteAuditResult
|
||||
{
|
||||
std::uint64_t segmentsTested = 0;
|
||||
std::uint64_t centerSamples = 0;
|
||||
std::uint64_t capsuleFieldSamples = 0;
|
||||
std::uint64_t blockedCapsuleSamples = 0;
|
||||
std::uint64_t supportProbeSamples = 0;
|
||||
std::uint64_t unsupportedCenterSamples = 0;
|
||||
std::uint64_t gradeFailures = 0;
|
||||
std::int64_t minimumClearanceSubvoxels = 0;
|
||||
std::int64_t maximumSupportDepthSubvoxels = 0;
|
||||
bool passed = false;
|
||||
};
|
||||
|
||||
VFCORE_API WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(
|
||||
const Heightfield2D& block, std::uint32_t boxCount = 256,
|
||||
std::uint64_t seed = 0x574f524c44415331ull);
|
||||
|
||||
VFCORE_API WorldAssemblyShelfThicknessAuditResult AuditHeightfieldShelfThickness(
|
||||
const Heightfield2D& block);
|
||||
|
||||
VFCORE_API WorldAssemblyRouteAuditResult AuditHeightfieldRoute(
|
||||
const Heightfield2D& block);
|
||||
|
||||
class VFCORE_API ResolvedWorldSnapshot
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -967,11 +967,38 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
|
||||
};
|
||||
const int32 Base=static_cast<int32>(NewVertices.size());
|
||||
const double Xs[4]={X0,X1,X1,X0},Ys[4]={Y0,Y0,Y1,Y1};
|
||||
std::array<double,4> CornerZ{};
|
||||
std::array<FVector,4> CornerNormals{};
|
||||
bool bValidPatch=true;
|
||||
for(int32 Corner=0;Corner<4;++Corner)
|
||||
{
|
||||
const double Z=SurfaceZ(Xs[Corner],Ys[Corner]);
|
||||
NewVertices.emplace_back(Xs[Corner]*25.0,Ys[Corner]*25.0,Z*25.0);
|
||||
NewNormals.push_back(Normal);
|
||||
const double PredictedZ=SurfaceZ(Xs[Corner],Ys[Corner]);
|
||||
const std::vector<double> CornerCrossings=FindFarStratumCrossings(
|
||||
Shape,Xs[Corner],Ys[Corner],ZMin,ZMax);
|
||||
if(CornerCrossings.empty()){bValidPatch=false;break;}
|
||||
CornerZ[Corner]=*std::min_element(CornerCrossings.begin(),CornerCrossings.end(),
|
||||
[PredictedZ](double A,double B)
|
||||
{return std::abs(A-PredictedZ)<std::abs(B-PredictedZ);});
|
||||
const float CornerGX=Density(Xs[Corner]+2,Ys[Corner],CornerZ[Corner])
|
||||
-Density(Xs[Corner]-2,Ys[Corner],CornerZ[Corner]);
|
||||
const float CornerGY=Density(Xs[Corner],Ys[Corner]+2,CornerZ[Corner])
|
||||
-Density(Xs[Corner],Ys[Corner]-2,CornerZ[Corner]);
|
||||
const float CornerGZ=Density(Xs[Corner],Ys[Corner],CornerZ[Corner]+2)
|
||||
-Density(Xs[Corner],Ys[Corner],CornerZ[Corner]-2);
|
||||
CornerNormals[Corner]=FVector(CornerGX,CornerGY,CornerGZ);
|
||||
if(CornerNormals[Corner].SizeSquared()<1.0e-8)
|
||||
CornerNormals[Corner]=Normal;
|
||||
else CornerNormals[Corner].Normalize();
|
||||
}
|
||||
// A center crossing alone cannot justify a large tangent quad: it can
|
||||
// bridge air over an isolated far-LOD feature and draw as a horizon slab.
|
||||
// Keep the fallback only when the source field has a matching crossing
|
||||
// at every corner, and place each corner on that source crossing.
|
||||
if(!bValidPatch)continue;
|
||||
for(int32 Corner=0;Corner<4;++Corner)
|
||||
{
|
||||
NewVertices.emplace_back(Xs[Corner]*25.0,Ys[Corner]*25.0,CornerZ[Corner]*25.0);
|
||||
NewNormals.push_back(CornerNormals[Corner]);
|
||||
if(bHasUVs)NewUVs.emplace_back(static_cast<float>((Xs[Corner]-Origin.X)/256.0),
|
||||
static_cast<float>((Ys[Corner]-Origin.Y)/256.0));
|
||||
if(bHasColors)NewColors.push_back(Color);
|
||||
|
||||
@@ -627,12 +627,42 @@ namespace VoxelForgeCore
|
||||
mesher.bGenerateSkirts=world.bGenerateSkirts;mesher.SkirtCells=static_cast<float>(world.SkirtCells);
|
||||
mesher.LODOctaveDrop=world.LODOctaveDrop;
|
||||
const int32 level=key.lod,extent=CHUNK_SIZE<<level;
|
||||
const int32 cells=level<world.FullResClipLevels?CHUNK_SIZE:FMath::Max(1,world.CoarseTileCells);
|
||||
const int32 step=FMath::Max(1,extent/cells);
|
||||
int64 sampleCount=0;std::vector<float> densityValues;
|
||||
const FVoxelTileKey nativeKey=ToNative(key);
|
||||
const FIntVector origin=nativeKey.OriginVoxels();
|
||||
int32 cells=level<world.FullResClipLevels?CHUNK_SIZE:FMath::Max(1,world.CoarseTileCells);
|
||||
int32 step=FMath::Max(1,extent/cells);
|
||||
bool bFeatureRefinedResolution=false;
|
||||
if(impl_->worldAssembly&&level<=2)
|
||||
{
|
||||
constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::array<std::int64_t,3> tileMin={
|
||||
static_cast<std::int64_t>(origin.X)*Scale,
|
||||
static_cast<std::int64_t>(origin.Y)*Scale,
|
||||
static_cast<std::int64_t>(origin.Z)*Scale};
|
||||
const std::array<std::int64_t,3> tileMax={
|
||||
static_cast<std::int64_t>(origin.X+extent)*Scale,
|
||||
static_cast<std::int64_t>(origin.Y+extent)*Scale,
|
||||
static_cast<std::int64_t>(origin.Z+extent)*Scale};
|
||||
for(const auto& stratum:impl_->worldAssembly->plan.strates)
|
||||
for(const auto& feature:stratum.features)
|
||||
{
|
||||
if(feature.type!=4)continue;
|
||||
bool overlaps=true;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
overlaps=overlaps&&feature.minimumSubvoxels[axis]<=tileMax[axis]
|
||||
&&feature.maximumSubvoxels[axis]>=tileMin[axis];
|
||||
if(overlaps)
|
||||
{
|
||||
// A two-voxel shelf is only one sample wide at LOD1's normal step.
|
||||
// Mesh feature-intersecting tiles at the one-voxel Core lattice so
|
||||
// the full-resolution block cannot turn into a one-cell plate.
|
||||
cells=extent;step=1;bFeatureRefinedResolution=true;break;
|
||||
}
|
||||
}
|
||||
}
|
||||
int64 sampleCount=0;std::vector<float> densityValues;
|
||||
TileMesh out;out.key=key;
|
||||
out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution;
|
||||
const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr;
|
||||
const bool bNeedsFarShell=bHasWorldAssembly&&key.lod>=7
|
||||
&&(tileShape.HasFarWorld()||impl_->shape->HasFarWorld());
|
||||
@@ -691,6 +721,18 @@ namespace VoxelForgeCore
|
||||
AppendFarStratumShells(world,farShape,nativeKey,step,mesh);
|
||||
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,mesh);
|
||||
}
|
||||
if(bHasWorldAssembly&&key.lod>=8&&mesh.NumCeilingTriangles>0)
|
||||
{
|
||||
// The finite sky cap remains in the composed field, queries, and local meshes.
|
||||
// At the coarsest clipmap level its large planar triangles read as a detached
|
||||
// horizon slab, so omit only that far render group after far-shell construction.
|
||||
const int32 KeptTriangleCount=std::max(0,mesh.Triangles.Num()/3-mesh.NumCeilingTriangles);
|
||||
const int32 KeptIndexCount=KeptTriangleCount*3;
|
||||
out.diagnostics.farCapTrianglesSuppressed=static_cast<std::uint64_t>(mesh.NumCeilingTriangles);
|
||||
mesh.Triangles.SetNum(KeptIndexCount);
|
||||
mesh.TriangleSkirtFaceMasks.SetNum(KeptTriangleCount);
|
||||
mesh.NumCeilingTriangles=0;
|
||||
}
|
||||
|
||||
out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
|
||||
if(bHasWorldAssembly)
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace VoxelForgeCore
|
||||
namespace world_assembly_detail
|
||||
{
|
||||
constexpr std::int64_t Q16 = 1ll << 16;
|
||||
constexpr std::int64_t MinimumCliffShelfThicknessSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
|
||||
constexpr std::int64_t MaxSerializedMagnitude = 1000000000000ll;
|
||||
constexpr std::size_t MaxBiomeCount = 8;
|
||||
constexpr std::size_t MaxStrateCount = 64;
|
||||
@@ -228,6 +229,60 @@ namespace world_assembly_detail
|
||||
|| !std::all_of(point.positionSubvoxels.begin(), point.positionSubvoxels.end(), ValidScalar))
|
||||
{ error = "world assembly route point has an invalid feature ID, position or radius"; return false; }
|
||||
}
|
||||
std::vector<std::uint64_t> routeIds;
|
||||
routeIds.reserve(stratum.route.size());
|
||||
for (const auto& point : stratum.route)
|
||||
{
|
||||
if (std::find(routeIds.begin(), routeIds.end(), point.stableFeatureId) != routeIds.end())
|
||||
{ error = "world assembly route point IDs must be unique"; return false; }
|
||||
routeIds.push_back(point.stableFeatureId);
|
||||
}
|
||||
for (std::size_t i = 1; i < stratum.route.size(); ++i)
|
||||
{
|
||||
std::uint64_t squared = 0;
|
||||
for (std::size_t axis = 0; axis < 3; ++axis)
|
||||
{
|
||||
const std::int64_t delta = stratum.route[i].positionSubvoxels[axis]
|
||||
- stratum.route[i - 1].positionSubvoxels[axis];
|
||||
if (std::abs(delta) > 4096 * WorldAssemblySubvoxelsPerVoxel)
|
||||
{ error = "world assembly route segment exceeds its bounded sweep length"; return false; }
|
||||
squared += static_cast<std::uint64_t>(delta * delta);
|
||||
}
|
||||
if (squared == 0)
|
||||
{ error = "world assembly route contains a zero-length segment"; return false; }
|
||||
}
|
||||
const auto& constraints = stratum.routeConstraints;
|
||||
if (constraints.version == 0)
|
||||
{
|
||||
if (constraints.capsuleRadiusSubvoxels != 0 || constraints.capsuleHeightSubvoxels != 0
|
||||
|| constraints.maximumGradeQ16 != 0 || constraints.sweepSpacingSubvoxels != 0
|
||||
|| constraints.supportProbeRadiusSubvoxels != 0
|
||||
|| constraints.supportSearchDepthSubvoxels != 0
|
||||
|| constraints.minimumClearanceSubvoxels != 0 || constraints.minimumSupportProbes != 0)
|
||||
{ error = "world assembly route constraints need a supported version"; return false; }
|
||||
}
|
||||
else if (constraints.version != 1
|
||||
|| !ValidScalar(constraints.capsuleRadiusSubvoxels)
|
||||
|| !ValidScalar(constraints.capsuleHeightSubvoxels)
|
||||
|| !ValidScalar(constraints.maximumGradeQ16)
|
||||
|| !ValidScalar(constraints.sweepSpacingSubvoxels)
|
||||
|| !ValidScalar(constraints.supportProbeRadiusSubvoxels)
|
||||
|| !ValidScalar(constraints.supportSearchDepthSubvoxels)
|
||||
|| !ValidScalar(constraints.minimumClearanceSubvoxels)
|
||||
|| constraints.capsuleRadiusSubvoxels <= 0
|
||||
|| constraints.capsuleHeightSubvoxels < 2 * constraints.capsuleRadiusSubvoxels
|
||||
|| constraints.maximumGradeQ16 <= 0 || constraints.maximumGradeQ16 > Q16
|
||||
|| constraints.sweepSpacingSubvoxels <= 0
|
||||
|| constraints.sweepSpacingSubvoxels > WorldAssemblySubvoxelsPerVoxel / 2
|
||||
|| constraints.supportProbeRadiusSubvoxels <= 0
|
||||
|| constraints.supportSearchDepthSubvoxels <= 0
|
||||
|| constraints.minimumClearanceSubvoxels < 0
|
||||
|| constraints.minimumSupportProbes == 0 || constraints.minimumSupportProbes > 5)
|
||||
{ error = "world assembly route support constraints are outside the supported range"; return false; }
|
||||
if (constraints.version == 1)
|
||||
for (const auto& point : stratum.route)
|
||||
if (point.radiusSubvoxels < constraints.capsuleRadiusSubvoxels)
|
||||
{ error = "world assembly route corridor is narrower than its player capsule"; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -310,6 +365,20 @@ namespace world_assembly_detail
|
||||
for (const auto value : feature.maximumSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8);
|
||||
for (const auto value : feature.parametersSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8);
|
||||
}
|
||||
if (stratum.routeConstraints.version != 0)
|
||||
{
|
||||
const auto& constraints = stratum.routeConstraints;
|
||||
HashWord(hash, 0x524f5554455631ull, 8);
|
||||
HashWord(hash, constraints.version, 4);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.capsuleRadiusSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.capsuleHeightSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.maximumGradeQ16), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.sweepSpacingSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.supportProbeRadiusSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.supportSearchDepthSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.minimumClearanceSubvoxels), 8);
|
||||
HashWord(hash, constraints.minimumSupportProbes, 4);
|
||||
}
|
||||
HashWord(hash, stratum.route.size(), 4);
|
||||
for (const auto& point : stratum.route)
|
||||
{
|
||||
@@ -440,6 +509,24 @@ namespace world_assembly_detail
|
||||
{ error = "world assembly stratum has malformed optional evaluator-2 fields"; return false; }
|
||||
if(hasWaterBoolean)stratum.waterEnabled=waterEnabled.Boolean(false);
|
||||
if(hasWaterNumber)stratum.waterEnabled=waterEnabled.Number()!=0.0;
|
||||
const VFJson::Value& routeConstraintsValue = item.Get("route_constraints");
|
||||
if (!std::holds_alternative<std::nullptr_t>(routeConstraintsValue.Data))
|
||||
{
|
||||
auto& constraints = stratum.routeConstraints;
|
||||
std::uint32_t minimumSupportProbes = 0;
|
||||
if (!routeConstraintsValue.IsObject()
|
||||
|| !ReadU32(routeConstraintsValue.Get("version"), constraints.version)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("capsule_radius_q"), constraints.capsuleRadiusSubvoxels)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("capsule_height_q"), constraints.capsuleHeightSubvoxels)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("maximum_grade_q16"), constraints.maximumGradeQ16)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("sweep_spacing_q"), constraints.sweepSpacingSubvoxels)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("support_probe_radius_q"), constraints.supportProbeRadiusSubvoxels)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("support_search_depth_q"), constraints.supportSearchDepthSubvoxels)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("minimum_clearance_q"), constraints.minimumClearanceSubvoxels)
|
||||
|| !ReadU32(routeConstraintsValue.Get("minimum_support_probes"), minimumSupportProbes))
|
||||
{ error = "world assembly route constraints are missing a fixed-scale field"; return false; }
|
||||
constraints.minimumSupportProbes = minimumSupportProbes;
|
||||
}
|
||||
for (const auto& featureValue : item.Get("features").AsArray())
|
||||
{
|
||||
WorldAssemblyFeatureV1 feature;
|
||||
@@ -1109,7 +1196,7 @@ namespace world_assembly_detail
|
||||
inline std::int64_t CliffShelfDensity(const WorldAssemblyFeatureV1& feature,
|
||||
const WorldAssemblyRecipeV1& recipe,const WorldAssemblyStratumPlanV1& plan,
|
||||
std::int64_t x,std::int64_t y,std::int64_t z,
|
||||
const HeightfieldPointSample& localColumn)
|
||||
const HeightfieldPointSample& localColumn,bool enforceMinimumThickness)
|
||||
{
|
||||
const auto& lo=feature.minimumSubvoxels;const auto& hi=feature.maximumSubvoxels;
|
||||
if(x<lo[0]||x>hi[0]||y<lo[1]||y>hi[1]||z<lo[2]||z>hi[2])
|
||||
@@ -1134,17 +1221,36 @@ namespace world_assembly_detail
|
||||
return std::numeric_limits<std::int64_t>::max()/4;
|
||||
const std::uint32_t vertical=SmoothQ16(static_cast<std::uint32_t>(
|
||||
std::clamp<std::int64_t>(aboveGround*Q16/height,0,Q16)));
|
||||
const std::int64_t sourceOffset=MulQ16(reach,vertical);
|
||||
// Evaluator v4 samples the source column on the shelf's root line. The offset
|
||||
// therefore follows horizontal progress through the block and stays fixed for
|
||||
// every z slice. Advancing it with z can make the shelf switch on at its top;
|
||||
// sampling the far end everywhere instead can pull a distant cliff cap across
|
||||
// the whole shelf. Earlier evaluators retain their height-dependent taper.
|
||||
const std::int64_t sourceOffset=enforceMinimumThickness
|
||||
?std::clamp<std::int64_t>(along,0,reach):MulQ16(reach,vertical);
|
||||
const std::int64_t sourceX=x-dirX*sourceOffset;
|
||||
const std::int64_t sourceY=y-dirY*sourceOffset;
|
||||
const HeightfieldPointSample source=CachedColumn(recipe,plan,sourceX,sourceY);
|
||||
if(source.groundHeightSubvoxels<=localColumn.groundHeightSubvoxels)
|
||||
return std::numeric_limits<std::int64_t>::max()/4;
|
||||
std::int64_t candidate=std::max({localColumn.groundHeightSubvoxels-z,
|
||||
z-source.groundHeightSubvoxels,-along,along-reach,
|
||||
std::abs(lateral)-halfWidth,-aboveGround,aboveGround-height});
|
||||
const std::uint32_t edgeFade=FeatureFadeQ16(feature,x,y,Feather);
|
||||
candidate+=static_cast<std::int64_t>(Q16-edgeFade)*Feather/Q16;
|
||||
const std::int64_t edgeInset=static_cast<std::int64_t>(Q16-edgeFade)*Feather/Q16;
|
||||
const std::int64_t sourceTop=enforceMinimumThickness
|
||||
?std::max(source.groundHeightSubvoxels,
|
||||
localColumn.groundHeightSubvoxels+MinimumCliffShelfThicknessSubvoxels)
|
||||
:source.groundHeightSubvoxels;
|
||||
std::int64_t candidate=std::max({localColumn.groundHeightSubvoxels-z,
|
||||
z-sourceTop,-aboveGround,aboveGround-height});
|
||||
if(enforceMinimumThickness)
|
||||
{
|
||||
// The feather narrows the shelf footprint. It must not offset the top and
|
||||
// bottom surfaces, or their vertical separation collapses into MC sheets.
|
||||
candidate=std::max({candidate,-along+edgeInset,along-reach+edgeInset,
|
||||
std::abs(lateral)-halfWidth+edgeInset});
|
||||
}
|
||||
else
|
||||
candidate=std::max({candidate,-along,along-reach,
|
||||
std::abs(lateral)-halfWidth})+edgeInset;
|
||||
return candidate;
|
||||
}
|
||||
|
||||
@@ -1192,6 +1298,26 @@ bool WorldAssemblyManifest::finalize(std::string& error)
|
||||
for (auto& stratum : plan.strates)
|
||||
{
|
||||
if (!world_assembly_detail::ValidateStratum(stratum, error)) return false;
|
||||
if (recipe.evaluatorVersion >= 4)
|
||||
{
|
||||
if (stratum.routeConstraints.version != 1 || stratum.route.size() < 2)
|
||||
{ error = "world assembly evaluator 4 requires a versioned entrance-to-exit route"; return false; }
|
||||
const std::int64_t bottomZ = static_cast<std::int64_t>(stratum.bottomChunkZ)
|
||||
* 32 * WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t topZ = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
|
||||
* 32 * WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t halfHeight = stratum.routeConstraints.capsuleHeightSubvoxels / 2;
|
||||
for (const auto& point : stratum.route)
|
||||
{
|
||||
if (point.positionSubvoxels[2] - halfHeight < bottomZ
|
||||
|| point.positionSubvoxels[2] + halfHeight > topZ)
|
||||
{ error = "world assembly route capsule escapes its finite stratum Z bounds"; return false; }
|
||||
}
|
||||
for (const auto& feature : stratum.features)
|
||||
if (feature.type == 4 && feature.parametersSubvoxels[2]
|
||||
< 2 * WorldAssemblySubvoxelsPerVoxel)
|
||||
{ error = "world assembly evaluator 4 cliff shelf is thinner than its two-voxel minimum"; return false; }
|
||||
}
|
||||
if (std::find(strateIds.begin(), strateIds.end(), stratum.strateIndex) != strateIds.end())
|
||||
{ error = "world assembly plan contains a duplicate strate index"; return false; }
|
||||
strateIds.push_back(stratum.strateIndex);
|
||||
@@ -1322,7 +1448,21 @@ std::string WorldAssemblyManifest::ToJson() const
|
||||
<< feature.parametersSubvoxels[0] << ',' << feature.parametersSubvoxels[1] << ','
|
||||
<< feature.parametersSubvoxels[2] << ',' << feature.parametersSubvoxels[3] << "]}";
|
||||
}
|
||||
out << "],\"route\":[";
|
||||
out << ']';
|
||||
if (stratum.routeConstraints.version != 0)
|
||||
{
|
||||
const auto& constraints = stratum.routeConstraints;
|
||||
out << ",\"route_constraints\":{\"version\":" << constraints.version
|
||||
<< ",\"capsule_radius_q\":" << constraints.capsuleRadiusSubvoxels
|
||||
<< ",\"capsule_height_q\":" << constraints.capsuleHeightSubvoxels
|
||||
<< ",\"maximum_grade_q16\":" << constraints.maximumGradeQ16
|
||||
<< ",\"sweep_spacing_q\":" << constraints.sweepSpacingSubvoxels
|
||||
<< ",\"support_probe_radius_q\":" << constraints.supportProbeRadiusSubvoxels
|
||||
<< ",\"support_search_depth_q\":" << constraints.supportSearchDepthSubvoxels
|
||||
<< ",\"minimum_clearance_q\":" << constraints.minimumClearanceSubvoxels
|
||||
<< ",\"minimum_support_probes\":" << constraints.minimumSupportProbes << '}';
|
||||
}
|
||||
out << ",\"route\":[";
|
||||
for (std::size_t p = 0; p < stratum.route.size(); ++p)
|
||||
{
|
||||
const auto& point = stratum.route[p];
|
||||
@@ -1391,7 +1531,8 @@ HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
|
||||
}
|
||||
else if(feature.type==4&&recipe_->evaluatorVersion>=3)
|
||||
{
|
||||
const std::int64_t shelf=CliffShelfDensity(feature,*recipe_,*plan_,x,y,z,out);
|
||||
const std::int64_t shelf=CliffShelfDensity(feature,*recipe_,*plan_,x,y,z,out,
|
||||
recipe_->evaluatorVersion>=4);
|
||||
out.densitySubvoxels=SmoothMinSubvoxels(out.densitySubvoxels,shelf,
|
||||
4*WorldAssemblySubvoxelsPerVoxel);
|
||||
}
|
||||
@@ -1592,6 +1733,9 @@ FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept
|
||||
(feature.maximumSubvoxels[2]-feature.minimumSubvoxels[2])/2}));
|
||||
if(feature.type==4)maximum=std::max(maximum,feature.parametersSubvoxels[2]
|
||||
+WorldAssemblySubvoxelsPerVoxel);
|
||||
if(feature.type==4&&recipe_->evaluatorVersion>=4)
|
||||
minimumFeature=std::min(minimumFeature,
|
||||
world_assembly_detail::MinimumCliffShelfThicknessSubvoxels);
|
||||
if(feature.type==3)maximum=std::max(maximum,std::abs(feature.parametersSubvoxels[0]));
|
||||
}
|
||||
out.maximumAbsoluteDisplacementSubvoxels = maximum;
|
||||
@@ -1746,4 +1890,203 @@ WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(const Heightfield2D& block
|
||||
&& result.unknownBounds == 0 && result.negativeControlDetected;
|
||||
return result;
|
||||
}
|
||||
|
||||
WorldAssemblyShelfThicknessAuditResult AuditHeightfieldShelfThickness(const Heightfield2D& block)
|
||||
{
|
||||
WorldAssemblyShelfThicknessAuditResult result;
|
||||
const auto* recipe=block.recipe();
|
||||
const auto* plan=block.plan();
|
||||
if(!block.isValid()||!recipe||!plan||recipe->evaluatorVersion<4)return result;
|
||||
constexpr std::int64_t sampleStep=WorldAssemblySubvoxelsPerVoxel/4;
|
||||
constexpr std::int64_t xyStep=WorldAssemblySubvoxelsPerVoxel/2;
|
||||
std::int64_t minimumObserved=std::numeric_limits<std::int64_t>::max();
|
||||
for(std::size_t featureIndex=0;featureIndex<plan->features.size();++featureIndex)
|
||||
{
|
||||
const auto& feature=plan->features[featureIndex];
|
||||
if(feature.type!=4)continue;
|
||||
++result.featuresTested;
|
||||
auto baselinePlan=*plan;
|
||||
baselinePlan.features.erase(baselinePlan.features.begin()+featureIndex);
|
||||
const Heightfield2D baseline(*recipe,baselinePlan);
|
||||
const std::int64_t firstX=feature.minimumSubvoxels[0]+WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t lastX=feature.maximumSubvoxels[0]-WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t firstY=feature.minimumSubvoxels[1]+WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t lastY=feature.maximumSubvoxels[1]-WorldAssemblySubvoxelsPerVoxel;
|
||||
for(std::int64_t y=firstY;y<=lastY;y+=xyStep)
|
||||
for(std::int64_t x=firstX;x<=lastX;x+=xyStep)
|
||||
{
|
||||
++result.columnsTested;
|
||||
const std::int64_t ground=baseline.point(x,y,0).groundHeightSubvoxels;
|
||||
const std::int64_t range=std::max<std::int64_t>(feature.parametersSubvoxels[2],
|
||||
world_assembly_detail::MinimumCliffShelfThicknessSubvoxels)+sampleStep;
|
||||
std::int64_t currentRun=0,legacyCurrentRun=0;
|
||||
std::int64_t currentStart=0,legacyCurrentStart=0;
|
||||
bool columnActive=false,columnSubVoxel=false,legacyColumnSubVoxel=false;
|
||||
const auto recordRun=[&](std::int64_t run,std::int64_t start,bool legacy)
|
||||
{
|
||||
if(run<=0)return;
|
||||
if(legacy)
|
||||
{
|
||||
if(run<WorldAssemblySubvoxelsPerVoxel)
|
||||
{++result.legacySubVoxelRuns;legacyColumnSubVoxel=true;}
|
||||
return;
|
||||
}
|
||||
columnActive=true;
|
||||
++result.negativeRuns;
|
||||
if(run<minimumObserved)
|
||||
{
|
||||
minimumObserved=run;
|
||||
result.minimumThicknessColumnXSubvoxels=x;
|
||||
result.minimumThicknessColumnYSubvoxels=y;
|
||||
result.minimumThicknessGroundSubvoxels=ground;
|
||||
result.minimumThicknessBottomSubvoxels=start;
|
||||
result.minimumThicknessTopSubvoxels=start+run-sampleStep;
|
||||
}
|
||||
if(run<WorldAssemblySubvoxelsPerVoxel)
|
||||
{++result.subVoxelRuns;columnSubVoxel=true;}
|
||||
};
|
||||
for(std::int64_t dz=sampleStep;dz<=range;dz+=sampleStep)
|
||||
{
|
||||
const std::int64_t z=ground+dz;
|
||||
const auto baselineSample=baseline.point(x,y,z);
|
||||
if(baselineSample.densitySubvoxels<=0)
|
||||
{
|
||||
recordRun(currentRun,currentStart,false);currentRun=0;
|
||||
recordRun(legacyCurrentRun,legacyCurrentStart,true);legacyCurrentRun=0;
|
||||
continue;
|
||||
}
|
||||
const auto composedSample=block.point(x,y,z);
|
||||
if(composedSample.densitySubvoxels<0)
|
||||
{
|
||||
if(currentRun==0)currentStart=z;
|
||||
currentRun+=sampleStep;
|
||||
}
|
||||
else {recordRun(currentRun,currentStart,false);currentRun=0;}
|
||||
|
||||
const std::int64_t oldShelf=world_assembly_detail::CliffShelfDensity(feature,*recipe,baselinePlan,
|
||||
x,y,z,baselineSample,false);
|
||||
const std::int64_t legacyDensity=world_assembly_detail::SmoothMinSubvoxels(
|
||||
baselineSample.densitySubvoxels,oldShelf,4*WorldAssemblySubvoxelsPerVoxel);
|
||||
if(legacyDensity<0)
|
||||
{if(legacyCurrentRun==0)legacyCurrentStart=z;legacyCurrentRun+=sampleStep;}
|
||||
else {recordRun(legacyCurrentRun,legacyCurrentStart,true);legacyCurrentRun=0;}
|
||||
}
|
||||
recordRun(currentRun,currentStart,false);
|
||||
recordRun(legacyCurrentRun,legacyCurrentStart,true);
|
||||
if(columnActive)++result.activeColumns;
|
||||
if(columnSubVoxel)++result.subVoxelColumns;
|
||||
if(legacyColumnSubVoxel)++result.legacySubVoxelColumns;
|
||||
}
|
||||
}
|
||||
if(minimumObserved!=std::numeric_limits<std::int64_t>::max())
|
||||
result.minimumObservedThicknessSubvoxels=minimumObserved;
|
||||
result.negativeControlDetected=result.legacySubVoxelColumns>0;
|
||||
result.passed=result.featuresTested>0&&result.activeColumns>0
|
||||
&&result.minimumObservedThicknessSubvoxels>=world_assembly_detail::MinimumCliffShelfThicknessSubvoxels
|
||||
&&result.subVoxelRuns==0&&result.negativeControlDetected;
|
||||
return result;
|
||||
}
|
||||
|
||||
WorldAssemblyRouteAuditResult AuditHeightfieldRoute(const Heightfield2D& block)
|
||||
{
|
||||
WorldAssemblyRouteAuditResult result;
|
||||
const auto* plan=block.plan();
|
||||
if(!block.isValid()||!plan||plan->routeConstraints.version!=1||plan->route.size()<2)
|
||||
return result;
|
||||
using namespace world_assembly_detail;
|
||||
const auto& constraints=plan->routeConstraints;
|
||||
const std::int64_t radius=constraints.capsuleRadiusSubvoxels;
|
||||
const std::int64_t halfHeight=constraints.capsuleHeightSubvoxels/2;
|
||||
const std::int64_t axisHalf=std::max<std::int64_t>(0,halfHeight-radius);
|
||||
const std::int64_t spacing=std::min<std::int64_t>(constraints.sweepSpacingSubvoxels,
|
||||
WorldAssemblySubvoxelsPerVoxel/2);
|
||||
const std::int64_t supportSpacing=std::min<std::int64_t>(spacing,
|
||||
WorldAssemblySubvoxelsPerVoxel/4);
|
||||
std::int64_t minimumClearance=std::numeric_limits<std::int64_t>::max();
|
||||
std::int64_t maximumSupportDepth=0;
|
||||
const std::array<std::array<std::int64_t,2>,5> supportOffsets{{
|
||||
{{0,0}},{{constraints.supportProbeRadiusSubvoxels,0}},
|
||||
{{-constraints.supportProbeRadiusSubvoxels,0}},
|
||||
{{0,constraints.supportProbeRadiusSubvoxels}},
|
||||
{{0,-constraints.supportProbeRadiusSubvoxels}}}};
|
||||
|
||||
const auto sweepCapsule=[&](const std::array<std::int64_t,3>& center)
|
||||
{
|
||||
++result.centerSamples;
|
||||
const std::int64_t footZ=center[2]-halfHeight;
|
||||
const std::int64_t firstDz=-halfHeight+constraints.minimumClearanceSubvoxels;
|
||||
for(std::int64_t dz=firstDz;dz<=halfHeight;dz+=spacing)
|
||||
{
|
||||
const std::int64_t capOffset=std::max<std::int64_t>(0,std::abs(dz)-axisHalf);
|
||||
const std::int64_t radialSquared=radius*radius-capOffset*capOffset;
|
||||
if(radialSquared<0)continue;
|
||||
for(std::int64_t oy=-radius;oy<=radius;oy+=spacing)
|
||||
for(std::int64_t ox=-radius;ox<=radius;ox+=spacing)
|
||||
{
|
||||
if(ox*ox+oy*oy>radialSquared)continue;
|
||||
const auto sample=block.point(center[0]+ox,center[1]+oy,center[2]+dz);
|
||||
++result.capsuleFieldSamples;
|
||||
minimumClearance=std::min(minimumClearance,sample.densitySubvoxels);
|
||||
if(sample.densitySubvoxels<constraints.minimumClearanceSubvoxels)
|
||||
++result.blockedCapsuleSamples;
|
||||
}
|
||||
}
|
||||
std::uint32_t supportedProbes=0;
|
||||
for(const auto& offset:supportOffsets)
|
||||
{
|
||||
++result.supportProbeSamples;
|
||||
bool supported=false;
|
||||
for(std::int64_t depth=supportSpacing;
|
||||
depth<=constraints.supportSearchDepthSubvoxels;depth+=supportSpacing)
|
||||
{
|
||||
const auto sample=block.point(center[0]+offset[0],center[1]+offset[1],footZ-depth);
|
||||
if(sample.densitySubvoxels<0)
|
||||
{
|
||||
supported=true;
|
||||
maximumSupportDepth=std::max(maximumSupportDepth,depth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(supported)++supportedProbes;
|
||||
}
|
||||
if(supportedProbes<constraints.minimumSupportProbes)
|
||||
++result.unsupportedCenterSamples;
|
||||
};
|
||||
|
||||
for(std::size_t segment=1;segment<plan->route.size();++segment)
|
||||
{
|
||||
++result.segmentsTested;
|
||||
const auto& from=plan->route[segment-1].positionSubvoxels;
|
||||
const auto& to=plan->route[segment].positionSubvoxels;
|
||||
const std::int64_t dx=to[0]-from[0],dy=to[1]-from[1],dz=to[2]-from[2];
|
||||
const std::uint64_t horizontalSquared=static_cast<std::uint64_t>(dx*dx+dy*dy);
|
||||
const std::uint64_t horizontalLength=IntegerSqrt(horizontalSquared);
|
||||
const std::uint64_t rise=static_cast<std::uint64_t>(std::abs(dz));
|
||||
if(horizontalLength==0||rise*static_cast<std::uint64_t>(Q16)
|
||||
>horizontalLength*static_cast<std::uint64_t>(constraints.maximumGradeQ16))
|
||||
++result.gradeFailures;
|
||||
const std::uint64_t length=IntegerSqrt(horizontalSquared
|
||||
+static_cast<std::uint64_t>(dz*dz));
|
||||
const std::uint64_t sampleCount=std::max<std::uint64_t>(1,
|
||||
(length+static_cast<std::uint64_t>(spacing)-1)/static_cast<std::uint64_t>(spacing));
|
||||
const std::uint64_t first=segment==1?0:1;
|
||||
for(std::uint64_t i=first;i<=sampleCount;++i)
|
||||
{
|
||||
const auto interpolate=[&](std::size_t axis)
|
||||
{
|
||||
const std::int64_t delta=to[axis]-from[axis];
|
||||
return from[axis]+delta*static_cast<std::int64_t>(i)
|
||||
/static_cast<std::int64_t>(sampleCount);
|
||||
};
|
||||
sweepCapsule({interpolate(0),interpolate(1),interpolate(2)});
|
||||
}
|
||||
}
|
||||
result.minimumClearanceSubvoxels=minimumClearance==std::numeric_limits<std::int64_t>::max()
|
||||
?0:minimumClearance;
|
||||
result.maximumSupportDepthSubvoxels=maximumSupportDepth;
|
||||
result.passed=result.segmentsTested>0&&result.centerSamples>0
|
||||
&&result.blockedCapsuleSamples==0&&result.unsupportedCenterSamples==0
|
||||
&&result.gradeFailures==0;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -1,7 +1,7 @@
|
||||
# VoxelForge standalone world
|
||||
|
||||
This C++20 program loads the owner snapshot from `data/owner_world.json`, runs the VoxelForge CPU
|
||||
pipeline, streams the clipmap with up to eight host worker threads, and displays it through Direct3D
|
||||
pipeline, streams the clipmap with up to sixteen host worker threads, and displays it through Direct3D
|
||||
11. `Core/` contains the Unreal-free `VoxelForgeCore` API and static-library target used by the
|
||||
standalone shape-world path. Unreal is not linked or needed at run time.
|
||||
|
||||
@@ -16,18 +16,18 @@ From the plugin root, run:
|
||||
|
||||
`Build.ps1 -Target Core` builds only `Build\VoxelForgeCore\VoxelForgeCore.lib`; `-Target All`
|
||||
builds that library and the viewer. `Core\core_sources.txt` is the shared translation-unit
|
||||
manifest. The build uses the configured VS 2022 `vcvars64.bat`, MSVC `/std:c++20`, `/O2`,
|
||||
`/arch:AVX2`, `/fp:precise` and `/MP8`, BelowNormal priority and affinity `0xFF00`. Pass another
|
||||
executable name to build beside any viewer that may be open by setting `-OutputName`, for example
|
||||
manifest. The defaults use MSVC `/std:c++20`, `/O2`, `/arch:AVX2`, `/fp:precise`, `/MP16`, Normal
|
||||
priority and affinity `0xFFFF`. Use `-CompileWorkers 8 -BuildPriority BelowNormal` for a lower-impact
|
||||
build. Pass another executable name to build beside any viewer that may be open by setting `-OutputName`, for example
|
||||
`VoxelForgeCoreStandalone_test.exe`. The executable finds the JSON under `Standalone\data\`.
|
||||
The isolated viewer links `Source\GpuFarViewerLinkStubs.cpp`; GPU Far rendering is not part of this
|
||||
CPU viewer target.
|
||||
|
||||
### Daytime resource limit
|
||||
### BuildHost resource controls
|
||||
|
||||
Run audits and benchmarks at BelowNormal priority and use no more than 8 of this PC's 16 logical
|
||||
CPUs. The viewer clamps worker requests to eight. `Build.ps1` bounds MSVC with `/MP8`, sets the
|
||||
build process to BelowNormal and affinity `0xFF00`, and retains `/fp:precise`. The verification
|
||||
runner in `Core\verification\RunMachine.ps1` applies the same limits to viewer runs and refuses to
|
||||
overwrite an existing log stem.
|
||||
The standalone viewer accepts `--workers=1..16`, `--process-priority=Normal` and
|
||||
`--affinity-all-cpu`. The verification runner in `Core\verification\RunMachine.ps1` retains its
|
||||
separate BelowNormal/8-worker limits and refuses to overwrite an existing log stem.
|
||||
|
||||
## Controls
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#include "../Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h"
|
||||
|
||||
// The isolated CPU viewer does not compile the GPU Far implementation. These link stubs keep
|
||||
// that optional path unavailable while preserving the Core and CPU mesh/capture paths.
|
||||
namespace VoxelForgeGpuFarViewer
|
||||
{
|
||||
std::filesystem::path ExecutablePrecompiledShaderDirectory(const std::filesystem::path& path)
|
||||
{ return path.parent_path(); }
|
||||
bool InitializeFarPipeline(ID3D11Device*,ID3D11DeviceContext*,const std::filesystem::path&,std::string& error)
|
||||
{ error="GPU far rendering is excluded from the isolated CPU viewer";return false; }
|
||||
bool QueueFarShaderWarmup(ID3D11Device*,ID3D11DeviceContext*,const std::filesystem::path&,
|
||||
bool,const std::string&,bool,std::string& error)
|
||||
{ error="GPU far rendering is excluded from the isolated CPU viewer";return false; }
|
||||
bool CompleteFarShaderWarmup(ID3D11Device*,ID3D11DeviceContext*,const std::filesystem::path&,std::string& error)
|
||||
{ error="GPU far rendering is excluded from the isolated CPU viewer";return false; }
|
||||
bool ReserveFarOutputPool(ID3D11Device*,ID3D11DeviceContext*,const std::filesystem::path&,
|
||||
std::uint32_t,std::uint32_t,std::string& error,std::uint32_t)
|
||||
{ error="GPU far rendering is excluded from the isolated CPU viewer";return false; }
|
||||
bool ReadFarTileRequiredVertexCounts(ID3D11Device*,ID3D11DeviceContext*,const std::filesystem::path&,
|
||||
const std::vector<std::uint32_t>&,std::vector<std::uint32_t>&,std::string& error)
|
||||
{ error="GPU far rendering is excluded from the isolated CPU viewer";return false; }
|
||||
bool ReadFarTileOutputStatuses(ID3D11Device*,ID3D11DeviceContext*,const std::filesystem::path&,
|
||||
const std::vector<std::uint32_t>&,std::vector<std::uint32_t>&,std::vector<std::uint32_t>&,
|
||||
std::string& error)
|
||||
{ error="GPU far rendering is excluded from the isolated CPU viewer";return false; }
|
||||
bool QueryFarGpuMemory(ID3D11Device*,ID3D11DeviceContext*,const std::filesystem::path&,
|
||||
std::uint64_t& usage,std::uint64_t& budget,std::string& error)
|
||||
{ usage=budget=0;error="GPU far rendering is excluded from the isolated CPU viewer";return false; }
|
||||
bool BeginFarTileBatch(ID3D11Device*,ID3D11DeviceContext*,const std::filesystem::path&,std::string& error)
|
||||
{ error="GPU far rendering is excluded from the isolated CPU viewer";return false; }
|
||||
bool BuildFarTile(ID3D11Device*,ID3D11DeviceContext*,VoxelForgeCore::World&,
|
||||
const VoxelForgeCore::TileKey&,const VoxelForgeCore::FarFieldTileInput*,
|
||||
const std::filesystem::path&,ID3D11Buffer** vertices,ID3D11Buffer** indices,
|
||||
ID3D11Buffer** args,ID3D11Buffer** connectorVertices,ID3D11Buffer** connectorIndices,
|
||||
std::uint32_t,std::uint32_t,std::uint32_t* argsOffset,ID3D11Query** completion,
|
||||
GpuTimingQueries*,CpuMesh*,double* enqueueMs,double* dispatchMs,std::string& error,
|
||||
bool,std::uint32_t,std::uint32_t)
|
||||
{
|
||||
if(vertices)*vertices=nullptr;
|
||||
if(indices)*indices=nullptr;
|
||||
if(args)*args=nullptr;
|
||||
if(connectorVertices)*connectorVertices=nullptr;
|
||||
if(connectorIndices)*connectorIndices=nullptr;
|
||||
if(argsOffset)*argsOffset=0;
|
||||
if(completion)*completion=nullptr;
|
||||
if(enqueueMs)*enqueueMs=0.0;
|
||||
if(dispatchMs)*dispatchMs=0.0;
|
||||
error="GPU far rendering is excluded from the isolated CPU viewer";
|
||||
return false;
|
||||
}
|
||||
bool SubmitFarTileBatch(ID3D11Device*,ID3D11DeviceContext*,const std::filesystem::path&,
|
||||
ID3D11Query**,GpuTimingQueries*,std::string& error)
|
||||
{ error="GPU far rendering is excluded from the isolated CPU viewer";return false; }
|
||||
bool ResolveGpuTiming(ID3D11DeviceContext*,GpuTimingQueries&,double& dispatchMs,
|
||||
std::uint64_t*,std::uint64_t*,std::uint64_t*)
|
||||
{ dispatchMs=0.0;return false; }
|
||||
bool ResolveGpuTimestampPair(ID3D11DeviceContext*,GpuTimingQueries&,std::uint64_t,double& dispatchMs)
|
||||
{ dispatchMs=0.0;return false; }
|
||||
}
|
||||
@@ -7878,6 +7878,152 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
return hash?hash:1;
|
||||
}
|
||||
|
||||
bool BuildFiniteStrateRoute(VoxelForgeCore::WorldAssemblyRecipeV1& recipe,
|
||||
VoxelForgeCore::WorldAssemblyStratumPlanV1& stratum,std::uint64_t& tested,
|
||||
std::uint64_t& gradeRejected,std::uint64_t& sweepRejected,std::string& error)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
constexpr std::int64_t PlayerRadiusQ=348; // 0.34 m in Core's 0.25 m voxel scale.
|
||||
constexpr std::int64_t PlayerHeightQ=1802; // 1.76 m in Core's 0.25 m voxel scale.
|
||||
constexpr std::int64_t HalfHeightQ=PlayerHeightQ/2;
|
||||
constexpr std::int64_t StepQ=128; // 0.5 Core voxel, finer than the density lattice.
|
||||
constexpr std::int64_t ProbeRadiusQ=174;
|
||||
constexpr std::int64_t SupportDepthQ=256;
|
||||
constexpr std::int64_t GradeQ16=17560; // tan(15 degrees), rounded upward.
|
||||
constexpr std::int64_t RouteRadiusQ=4*WorldAssemblySubvoxelsPerVoxel;
|
||||
struct Direction {std::int32_t x,y;};
|
||||
constexpr std::array<Direction,8> directions{{
|
||||
{65536,0},{46341,46341},{0,65536},{-46341,46341},
|
||||
{-65536,0},{-46341,-46341},{0,-65536},{46341,-46341}}};
|
||||
struct Candidate
|
||||
{
|
||||
std::int64_t score=0;
|
||||
std::int32_t centerX=0,centerY=0,direction=0;
|
||||
std::vector<WorldAssemblyRoutePointV1> points;
|
||||
};
|
||||
|
||||
auto& constraints=stratum.routeConstraints;
|
||||
constraints.version=1;
|
||||
constraints.capsuleRadiusSubvoxels=PlayerRadiusQ;
|
||||
constraints.capsuleHeightSubvoxels=PlayerHeightQ;
|
||||
constraints.maximumGradeQ16=GradeQ16;
|
||||
constraints.sweepSpacingSubvoxels=StepQ;
|
||||
constraints.supportProbeRadiusSubvoxels=ProbeRadiusQ;
|
||||
constraints.supportSearchDepthSubvoxels=SupportDepthQ;
|
||||
constraints.minimumClearanceSubvoxels=8;
|
||||
constraints.minimumSupportProbes=3;
|
||||
|
||||
const Heightfield2D finalField(recipe,stratum);
|
||||
if(!finalField.isValid())
|
||||
{error="route planner received an invalid final composed field";return false;}
|
||||
std::vector<Candidate> candidates;
|
||||
constexpr std::int32_t halfLength=128;
|
||||
constexpr std::int32_t waypointSpacing=8;
|
||||
for(std::int32_t centerY=-256;centerY<=256;centerY+=64)
|
||||
for(std::int32_t centerX=-256;centerX<=256;centerX+=64)
|
||||
for(std::int32_t directionIndex=0;directionIndex<static_cast<std::int32_t>(directions.size());++directionIndex)
|
||||
{
|
||||
Candidate candidate;
|
||||
candidate.centerX=centerX;candidate.centerY=centerY;candidate.direction=directionIndex;
|
||||
candidate.points.reserve(33);
|
||||
std::int64_t previousGround=0,previousX=0,previousY=0;
|
||||
bool gradePass=true;
|
||||
for(std::int32_t index=0;index<=32;++index)
|
||||
{
|
||||
const std::int64_t along=(index-16)*waypointSpacing;
|
||||
const std::int64_t x=centerX+std::llround(
|
||||
static_cast<double>(along)*directions[directionIndex].x/65536.0);
|
||||
const std::int64_t y=centerY+std::llround(
|
||||
static_cast<double>(along)*directions[directionIndex].y/65536.0);
|
||||
const auto column=finalField.point(WorldAssemblyFixed(static_cast<double>(x)),
|
||||
WorldAssemblyFixed(static_cast<double>(y)),0);
|
||||
const std::int64_t horizontalQ=static_cast<std::int64_t>(std::llround(
|
||||
std::hypot(static_cast<double>(x-previousX),static_cast<double>(y-previousY))
|
||||
*WorldAssemblySubvoxelsPerVoxel));
|
||||
if(index>0)
|
||||
{
|
||||
const std::int64_t riseQ=std::abs(column.groundHeightSubvoxels-previousGround);
|
||||
if(horizontalQ==0||riseQ*65536>horizontalQ*GradeQ16)
|
||||
{gradePass=false;break;}
|
||||
candidate.score+=riseQ*65536/std::max<std::int64_t>(1,horizontalQ);
|
||||
}
|
||||
WorldAssemblyRoutePointV1 point;
|
||||
point.positionSubvoxels={WorldAssemblyFixed(static_cast<double>(x)),
|
||||
WorldAssemblyFixed(static_cast<double>(y)),column.groundHeightSubvoxels+HalfHeightQ};
|
||||
point.radiusSubvoxels=RouteRadiusQ;
|
||||
point.stableFeatureId=WorldAssemblyFeatureId(recipe.seed,
|
||||
static_cast<std::int64_t>(x),static_cast<std::int64_t>(y),
|
||||
0x52540000u+static_cast<std::uint32_t>(index));
|
||||
candidate.points.push_back(point);
|
||||
previousGround=column.groundHeightSubvoxels;previousX=x;previousY=y;
|
||||
}
|
||||
++tested;
|
||||
if(gradePass)candidates.push_back(std::move(candidate));
|
||||
else ++gradeRejected;
|
||||
}
|
||||
std::stable_sort(candidates.begin(),candidates.end(),[](const Candidate& a,const Candidate& b)
|
||||
{
|
||||
if(a.score!=b.score)return a.score<b.score;
|
||||
if(a.centerY!=b.centerY)return a.centerY<b.centerY;
|
||||
if(a.centerX!=b.centerX)return a.centerX<b.centerX;
|
||||
return a.direction<b.direction;
|
||||
});
|
||||
std::uint64_t sweepAttempt=0,repairAttempt=0;
|
||||
VoxelForgeCore::WorldAssemblyRouteAuditResult lastAudit{};
|
||||
for(auto& candidate:candidates)
|
||||
{
|
||||
stratum.route=candidate.points;
|
||||
std::int64_t liftQ=0;
|
||||
constexpr std::int64_t RepairStepQ=64;
|
||||
constexpr std::int64_t MaximumRepairLiftQ=SupportDepthQ-RepairStepQ;
|
||||
for(;;)
|
||||
{
|
||||
if(liftQ>0)
|
||||
for(std::size_t pointIndex=0;pointIndex<stratum.route.size();++pointIndex)
|
||||
stratum.route[pointIndex].positionSubvoxels[2]
|
||||
=candidate.points[pointIndex].positionSubvoxels[2]+liftQ;
|
||||
const Heightfield2D candidateField(recipe,stratum);
|
||||
const auto audit=AuditHeightfieldRoute(candidateField);
|
||||
lastAudit=audit;
|
||||
if(audit.passed)
|
||||
{
|
||||
if(liftQ>0)
|
||||
std::printf("[WorldAssemblerRouteRepaired] uniform_lift_q=%lld support_limit_q=%lld\n",
|
||||
static_cast<long long>(liftQ),static_cast<long long>(SupportDepthQ));
|
||||
return true;
|
||||
}
|
||||
if(audit.blockedCapsuleSamples==0||liftQ>=MaximumRepairLiftQ)break;
|
||||
const std::int64_t needed=std::max<std::int64_t>(1,
|
||||
constraints.minimumClearanceSubvoxels-audit.minimumClearanceSubvoxels);
|
||||
const std::int64_t steps=(needed+RepairStepQ-1)/RepairStepQ;
|
||||
const std::int64_t nextLift=std::max(liftQ+RepairStepQ,liftQ+steps*RepairStepQ);
|
||||
if(nextLift>MaximumRepairLiftQ)break;
|
||||
liftQ=nextLift;
|
||||
++repairAttempt;
|
||||
}
|
||||
if(sweepAttempt<3)
|
||||
std::printf("[WorldAssemblerRouteCandidateRejected] ordinal=%llu blocked=%llu unsupported=%llu grade_failures=%llu minimum_clearance_q=%lld\n",
|
||||
static_cast<unsigned long long>(sweepAttempt),
|
||||
static_cast<unsigned long long>(lastAudit.blockedCapsuleSamples),
|
||||
static_cast<unsigned long long>(lastAudit.unsupportedCenterSamples),
|
||||
static_cast<unsigned long long>(lastAudit.gradeFailures),
|
||||
static_cast<long long>(lastAudit.minimumClearanceSubvoxels));
|
||||
++sweepAttempt;
|
||||
++sweepRejected;
|
||||
}
|
||||
std::printf("[WorldAssemblerRouteSearchFailed] tested=%llu grade_rejected=%llu sweepable_candidates=%llu sweep_rejected=%llu last_blocked=%llu last_unsupported=%llu last_grade_failures=%llu\n",
|
||||
static_cast<unsigned long long>(tested),static_cast<unsigned long long>(gradeRejected),
|
||||
static_cast<unsigned long long>(candidates.size()),static_cast<unsigned long long>(sweepRejected),
|
||||
static_cast<unsigned long long>(lastAudit.blockedCapsuleSamples),
|
||||
static_cast<unsigned long long>(lastAudit.unsupportedCenterSamples),
|
||||
static_cast<unsigned long long>(lastAudit.gradeFailures));
|
||||
std::printf("[WorldAssemblerRouteRepairSummary] repair_attempts=%llu\n",
|
||||
static_cast<unsigned long long>(repairAttempt));
|
||||
stratum.route.clear();
|
||||
error="finite-strate route candidates failed the composed-field capsule and support sweep";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AttachRound1WorldAssemblyPrototype(FStandaloneOwnerWorld& owner,VFJson::Value& root,
|
||||
std::string& error,const std::string& manifestOutputPath)
|
||||
{
|
||||
@@ -8067,9 +8213,25 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
stratum.features.push_back(shelf);
|
||||
}
|
||||
}
|
||||
std::uint64_t routeCandidates=0,routeGradeRejected=0,routeSweepRejected=0;
|
||||
if(!BuildFiniteStrateRoute(recipe,stratum,routeCandidates,routeGradeRejected,
|
||||
routeSweepRejected,error))return false;
|
||||
std::printf("[WorldAssemblerRoutePlan] candidates=%llu grade_rejected=%llu sweep_rejected=%llu accepted_points=%zu capsule_m=0.34x1.76 corridor_radius_vox=4 support_probe_points=5 minimum_support_probes=%u status=PASS\n",
|
||||
static_cast<unsigned long long>(routeCandidates),
|
||||
static_cast<unsigned long long>(routeGradeRejected),
|
||||
static_cast<unsigned long long>(routeSweepRejected),stratum.route.size(),
|
||||
stratum.routeConstraints.minimumSupportProbes);
|
||||
manifest.plan.strates.push_back(std::move(stratum));
|
||||
if(!manifest.finalize(error))return false;
|
||||
const std::string manifestText=manifest.ToJson();
|
||||
const auto roundTrip=VoxelForgeCore::WorldAssemblyManifest::FromJson(manifestText,error);
|
||||
if(!roundTrip||!roundTrip->validate(error)||roundTrip->ToJson()!=manifestText
|
||||
||roundTrip->contentHash!=manifest.contentHash)
|
||||
{if(error.empty())error="world assembly canonical plan serialization did not round-trip";return false;}
|
||||
std::printf("[WorldAssemblerPlanSerialization] bytes=%zu canonical_round_trip=true route_points=%zu plan_hash=%016llx manifest_hash=%016llx status=PASS\n",
|
||||
manifestText.size(),manifest.plan.strates.front().route.size(),
|
||||
static_cast<unsigned long long>(manifest.plan.contentHash),
|
||||
static_cast<unsigned long long>(manifest.contentHash));
|
||||
if(!manifestOutputPath.empty())
|
||||
{
|
||||
const std::filesystem::path outputPath(manifestOutputPath);
|
||||
@@ -8134,6 +8296,55 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
static_cast<unsigned long long>(bounds.exceptionPointSamples),
|
||||
bounds.negativeControlDetected?"true":"false",bounds.passed?"PASS":"FAIL");
|
||||
|
||||
const auto shelfThickness=VoxelForgeCore::AuditHeightfieldShelfThickness(block);
|
||||
std::printf("[WorldAssemblerShelfThicknessAudit] features=%llu columns=%llu active_columns=%llu negative_runs=%llu minimum_observed_vox=%.3f sub_voxel_runs=%llu sub_voxel_columns=%llu legacy_sub_voxel_runs=%llu legacy_sub_voxel_control_columns=%llu negative_control_detected=%s status=%s\n",
|
||||
static_cast<unsigned long long>(shelfThickness.featuresTested),
|
||||
static_cast<unsigned long long>(shelfThickness.columnsTested),
|
||||
static_cast<unsigned long long>(shelfThickness.activeColumns),
|
||||
static_cast<unsigned long long>(shelfThickness.negativeRuns),
|
||||
static_cast<double>(shelfThickness.minimumObservedThicknessSubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
||||
static_cast<unsigned long long>(shelfThickness.subVoxelRuns),
|
||||
static_cast<unsigned long long>(shelfThickness.subVoxelColumns),
|
||||
static_cast<unsigned long long>(shelfThickness.legacySubVoxelRuns),
|
||||
static_cast<unsigned long long>(shelfThickness.legacySubVoxelColumns),
|
||||
shelfThickness.negativeControlDetected?"true":"false",
|
||||
shelfThickness.passed?"PASS":"FAIL");
|
||||
std::printf("[WorldAssemblerShelfMinimumColumn] x_vox=%.3f y_vox=%.3f ground_z_vox=%.3f run_bottom_z_vox=%.3f run_top_z_vox=%.3f\n",
|
||||
static_cast<double>(shelfThickness.minimumThicknessColumnXSubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
||||
static_cast<double>(shelfThickness.minimumThicknessColumnYSubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
||||
static_cast<double>(shelfThickness.minimumThicknessGroundSubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
||||
static_cast<double>(shelfThickness.minimumThicknessBottomSubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
||||
static_cast<double>(shelfThickness.minimumThicknessTopSubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
|
||||
const auto routeAudit=VoxelForgeCore::AuditHeightfieldRoute(block);
|
||||
auto raisedRoutePlan=plan;
|
||||
for(auto& point:raisedRoutePlan.route)
|
||||
point.positionSubvoxels[2]+=16*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const VoxelForgeCore::Heightfield2D raisedRouteBlock(manifest->recipe,raisedRoutePlan);
|
||||
const auto raisedRouteAudit=VoxelForgeCore::AuditHeightfieldRoute(raisedRouteBlock);
|
||||
const bool routeSupportNegativeControl=!raisedRouteAudit.passed
|
||||
&&raisedRouteAudit.unsupportedCenterSamples>0;
|
||||
std::printf("[WorldAssemblerRouteSweep] points=%zu segments=%llu swept_centers=%llu capsule_field_samples=%llu blocked_samples=%llu support_probes=%llu unsupported_centers=%llu grade_failures=%llu minimum_clearance_q=%lld maximum_support_depth_q=%lld unsupported_control_centers=%llu negative_control_detected=%s collision_readiness=%s status=%s\n",
|
||||
plan.route.size(),static_cast<unsigned long long>(routeAudit.segmentsTested),
|
||||
static_cast<unsigned long long>(routeAudit.centerSamples),
|
||||
static_cast<unsigned long long>(routeAudit.capsuleFieldSamples),
|
||||
static_cast<unsigned long long>(routeAudit.blockedCapsuleSamples),
|
||||
static_cast<unsigned long long>(routeAudit.supportProbeSamples),
|
||||
static_cast<unsigned long long>(routeAudit.unsupportedCenterSamples),
|
||||
static_cast<unsigned long long>(routeAudit.gradeFailures),
|
||||
static_cast<long long>(routeAudit.minimumClearanceSubvoxels),
|
||||
static_cast<long long>(routeAudit.maximumSupportDepthSubvoxels),
|
||||
static_cast<unsigned long long>(raisedRouteAudit.unsupportedCenterSamples),
|
||||
routeSupportNegativeControl?"true":"false",
|
||||
routeAudit.passed?"analytic_field_sweep_ready":"blocked",
|
||||
routeAudit.passed&&routeSupportNegativeControl?"PASS":"FAIL");
|
||||
|
||||
bool cliffRegressionPassing=false;
|
||||
const auto cliffIt=std::find_if(plan.features.begin(),plan.features.end(),
|
||||
[](const auto& feature){return feature.type==4;});
|
||||
@@ -8496,12 +8707,26 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
coreQueryMaximumError=std::max(coreQueryMaximumError,std::abs(expected-actual));
|
||||
++coreQuerySamples;
|
||||
}
|
||||
for(const auto& routePoint:plan.route)
|
||||
{
|
||||
const auto& position=routePoint.positionSubvoxels;
|
||||
const double x=static_cast<double>(position[0])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double y=static_cast<double>(position[1])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double z=static_cast<double>(position[2])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double expected=static_cast<double>(block.point(position[0],position[1],position[2])
|
||||
.densitySubvoxels)/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const double actual=coreWorld.densityAt({x,y,z});
|
||||
coreQueryMaximumError=std::max(coreQueryMaximumError,std::abs(expected-actual));
|
||||
++coreQuerySamples;
|
||||
}
|
||||
}
|
||||
const bool coreQueryPass=coreCompiled&&coreQuerySamples==45&&coreQueryMaximumError<=1.0/256.0;
|
||||
std::printf("[WorldAssemblerCoreQuery] compiled=%s compile_ms=%.3f samples=%llu max_abs_error=%.8f render_meshes_generated=0 readiness=%s status=%s error=%s\n",
|
||||
const std::uint64_t expectedCoreQuerySamples=45+plan.route.size();
|
||||
const bool coreQueryPass=coreCompiled&&coreQuerySamples==expectedCoreQuerySamples
|
||||
&&coreQueryMaximumError<=1.0/256.0;
|
||||
std::printf("[WorldAssemblerServerFieldQuery] role=server_api_path=World::densityAt compiled=%s compile_ms=%.3f samples=%llu route_waypoints=%zu max_abs_error=%.8f render_meshes_generated=0 local_field_query=%s collision_mesh_cooking=not_in_standalone_core status=%s error=%s\n",
|
||||
coreCompiled?"true":"false",coreCompileMs,
|
||||
static_cast<unsigned long long>(coreQuerySamples),coreQueryMaximumError,
|
||||
coreQueryPass?"field_ready_without_meshes":"unavailable",coreQueryPass?"PASS":"FAIL",
|
||||
static_cast<unsigned long long>(coreQuerySamples),plan.route.size(),coreQueryMaximumError,
|
||||
coreQueryPass?"ready_without_meshes":"unavailable",coreQueryPass?"PASS":"FAIL",
|
||||
coreError.empty()?"none":coreError.c_str());
|
||||
if(!captureDirectory.empty())
|
||||
{
|
||||
@@ -8582,7 +8807,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
ownerSaved&&nativeSaved?"PASS":"FAIL");
|
||||
}
|
||||
std::printf("[WorldAssemblerFullViewTiming] status=NOT_MEASURED_IN_AUDIT_MODE; capture_viewer_runs_report_separately\n");
|
||||
return bounds.passed&&cliffRegressionPassing&&seamClassificationPassing
|
||||
std::printf("[WorldAssemblerReadiness] local_field_query=%s analytic_collision_validation=%s physics_collision_cooking=deferred_to_Unreal_adapter render_mesh_readiness=reported_by_matched_view_runs\n",
|
||||
coreQueryPass?"ready_without_render_meshes":"not_ready",
|
||||
routeAudit.passed?"route_sweep_ready":"not_ready");
|
||||
return bounds.passed&&shelfThickness.passed&&routeAudit.passed
|
||||
&&routeSupportNegativeControl&&cliffRegressionPassing&&seamClassificationPassing
|
||||
&&deterministic&&coreQueryPass?0:11;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user