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:
Fr0zkaandClaude Opus 5.5 committed 2026-10-09 02:12:39 +02:00
1 parent 2c20f9eaea
commit 20c6d86f0e
11 files changed
+839 -47

No files matched your search

+9 -1
View File
@@ -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. | | 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. | | 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. | | 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. | | 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. | | 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. | | 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 continues to verify the opt-in Unreal route and its 25 cm Core scale; it does not certify the missing
archetypes. 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) ## 9. Multiplayer model (listen-server first, dedicated-friendly)
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a > **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
+7
View File
@@ -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 [`Standalone/UNREAL_INTEGRATION_REPORT.md`](Standalone/UNREAL_INTEGRATION_REPORT.md) for build and
headless evidence and the run ledger. 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 `RunStandaloneWorldExport` in `VoxelForgeExploreCommandlet.cpp` serializes owner assets on the game
thread to schema-v1 JSON. The adapter parses it through `ResolvedWorldSnapshot::FromJson` and keeps 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), UObjects off Core workers. The runtime switch is `AVoxelWorld::bUseVoxelForgeCore` (default false),
+9 -6
View File
@@ -1,13 +1,15 @@
[CmdletBinding()] [CmdletBinding()]
param( param(
[string]$OutputName = 'VoxelForgeCoreStandalone.exe', [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' $ErrorActionPreference = 'Stop'
$BuildHostProcess = Get-Process -Id $PID $BuildHostProcess = Get-Process -Id $PID
$BuildHostProcess.PriorityClass = 'BelowNormal' $BuildHostProcess.PriorityClass = $BuildPriority
$BuildHostProcess.ProcessorAffinity = [IntPtr]0xFF00 $BuildHostProcess.ProcessorAffinity = [IntPtr]0xFFFF
$Root = [IO.Path]::GetFullPath($PSScriptRoot) $Root = [IO.Path]::GetFullPath($PSScriptRoot)
$BuildDir = Join-Path $Root 'Build\VoxelForgeCore' $BuildDir = Join-Path $Root 'Build\VoxelForgeCore'
$BinDir = Join-Path $Root 'bin' $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\Public')),
('/I"{0}"' -f (Join-Path $Root 'Source\VoxelForge\Private')) ('/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' $CoreRsp = Join-Path $BuildDir 'core-compile.rsp'
$CoreRspLines = $CommonFlags + @('/c') + @($CompileSources | ForEach-Object { '"{0}"' -f $_ }) $CoreRspLines = $CommonFlags + @('/c') + @($CompileSources | ForEach-Object { '"{0}"' -f $_ })
[IO.File]::WriteAllLines($CoreRsp,$CoreRspLines,[Text.UTF8Encoding]::new($false)) [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 } if ($CoreSourceNames[$Index].Replace('\','/') -eq 'Core/src/VoxelForgeCore.cpp') { continue }
$LegacySources += $CoreSources[$Index] $LegacySources += $CoreSources[$Index]
} }
$LegacySources += (Join-Path $Root 'Source\GpuFarViewerLinkStubs.cpp')
$LegacyObjects = @($LegacySources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) }) $LegacyObjects = @($LegacySources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) })
$LegacyRspLines = $CommonFlags + @('/c') + @($LegacySources | ForEach-Object { '"{0}"' -f $_ }) $LegacyRspLines = $CommonFlags + @('/c') + @($LegacySources | ForEach-Object { '"{0}"' -f $_ })
[IO.File]::WriteAllLines($LegacyRsp,$LegacyRspLines,[Text.UTF8Encoding]::new($false)) [IO.File]::WriteAllLines($LegacyRsp,$LegacyRspLines,[Text.UTF8Encoding]::new($false))
@@ -104,8 +107,8 @@ $ViewerRspLines = $CommonFlags + @('/Fe"{0}"' -f $OutputPath) + $LegacyObjects +
Push-Location $BuildDir Push-Location $BuildDir
try { try {
# One cl process with /MP8 bounds compiler work to eight processes; MSVC, lib.exe, and the # MSVC's /MP count and the build host's Normal-priority 16-CPU mask are configurable for
# viewer link inherit BelowNormal priority and the 0xFF00 process affinity from this host. # 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 $CommandLine = 'call "{0}" >nul && cl.exe @"{1}" && lib.exe @"{2}"' -f $VcVars,$CoreRsp,$LibRsp
& $env:ComSpec /d /s /c $CommandLine & $env:ComSpec /d /s /c $CommandLine
if ($LASTEXITCODE -eq -1073741502) { throw 'A build process exited with 0xC0000142; stopping as required.' } if ($LASTEXITCODE -eq -1073741502) { throw 'A build process exited with 0xC0000142; stopping as required.' }
+26 -10
View File
@@ -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 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. 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 `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 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 with hashes, no generated mesh or voxel samples. The current review payload is written to
[`round2/world_assembly.json`](../Research/WorldAssemblerM1/round2/world_assembly.json); the [`round4/world_assembly.json`](../Research/WorldAssemblerM1/round4/world_assembly.json).
evaluator-v1 sample remains at [`round1-manifest.json`](../Research/WorldAssemblerM1/round1-manifest.json). Evaluator 1–3 payloads remain readable with their original hashes and field semantics. Evaluator 4
Round 4 still needs to connect this payload to the season asset/hash path. 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`), 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 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, 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 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, 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 stable-ID features, fixed-season water, route points and (for evaluator 4) player-capsule, grade,
validated before a manifest is accepted. 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 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 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 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 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 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 size. Evaluator 4 defines the shelf's minimum solid thickness as two Core voxels (0.5 m) and applies
explicitly bounded evaluator-v2 feature boxes. 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 `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 `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 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. 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 ## 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 `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. 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 `classifyBox(origin, step, cells)` returns `allSolid`, `allAir` or `mixed` for mesher tile
@@ -82,6 +82,8 @@ namespace VoxelForgeCore
double meshingMilliseconds = 0.0; double meshingMilliseconds = 0.0;
bool tileWasClassified = false; bool tileWasClassified = false;
bool meshingSkipped = false; bool meshingSkipped = false;
bool featureRefinedResolution = false;
std::uint64_t farCapTrianglesSuppressed = 0;
}; };
struct TileMesh struct TileMesh
@@ -126,7 +128,7 @@ namespace VoxelForgeCore
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles. // 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::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1; 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 enum class FieldChannel : std::uint32_t
{ {
@@ -193,11 +195,26 @@ namespace VoxelForgeCore
struct WorldAssemblyRoutePointV1 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::uint64_t stableFeatureId = 0;
std::array<std::int64_t, 3> positionSubvoxels{}; std::array<std::int64_t, 3> positionSubvoxels{};
std::int64_t radiusSubvoxels = 0; 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 struct WorldAssemblyFeatureV1
{ {
// Stable numeric type IDs are part of the plan contract: 1 is a heightfield cliff plane, // 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; std::int64_t waterSurfaceSubvoxels = 0;
bool waterEnabled = false; bool waterEnabled = false;
std::vector<WorldAssemblyFeatureV1> features; std::vector<WorldAssemblyFeatureV1> features;
WorldAssemblyRouteConstraintsV1 routeConstraints;
std::vector<WorldAssemblyRoutePointV1> route; std::vector<WorldAssemblyRoutePointV1> route;
std::uint64_t contentHash = 0; std::uint64_t contentHash = 0;
}; };
@@ -357,10 +375,50 @@ namespace VoxelForgeCore
bool passed = false; 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( VFCORE_API WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(
const Heightfield2D& block, std::uint32_t boxCount = 256, const Heightfield2D& block, std::uint32_t boxCount = 256,
std::uint64_t seed = 0x574f524c44415331ull); std::uint64_t seed = 0x574f524c44415331ull);
VFCORE_API WorldAssemblyShelfThicknessAuditResult AuditHeightfieldShelfThickness(
const Heightfield2D& block);
VFCORE_API WorldAssemblyRouteAuditResult AuditHeightfieldRoute(
const Heightfield2D& block);
class VFCORE_API ResolvedWorldSnapshot class VFCORE_API ResolvedWorldSnapshot
{ {
public: public:
+30 -3
View File
@@ -967,11 +967,38 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
}; };
const int32 Base=static_cast<int32>(NewVertices.size()); const int32 Base=static_cast<int32>(NewVertices.size());
const double Xs[4]={X0,X1,X1,X0},Ys[4]={Y0,Y0,Y1,Y1}; 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) for(int32 Corner=0;Corner<4;++Corner)
{ {
const double Z=SurfaceZ(Xs[Corner],Ys[Corner]); const double PredictedZ=SurfaceZ(Xs[Corner],Ys[Corner]);
NewVertices.emplace_back(Xs[Corner]*25.0,Ys[Corner]*25.0,Z*25.0); const std::vector<double> CornerCrossings=FindFarStratumCrossings(
NewNormals.push_back(Normal); 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), if(bHasUVs)NewUVs.emplace_back(static_cast<float>((Xs[Corner]-Origin.X)/256.0),
static_cast<float>((Ys[Corner]-Origin.Y)/256.0)); static_cast<float>((Ys[Corner]-Origin.Y)/256.0));
if(bHasColors)NewColors.push_back(Color); if(bHasColors)NewColors.push_back(Color);
+45 -3
View File
@@ -627,12 +627,42 @@ namespace VoxelForgeCore
mesher.bGenerateSkirts=world.bGenerateSkirts;mesher.SkirtCells=static_cast<float>(world.SkirtCells); mesher.bGenerateSkirts=world.bGenerateSkirts;mesher.SkirtCells=static_cast<float>(world.SkirtCells);
mesher.LODOctaveDrop=world.LODOctaveDrop; mesher.LODOctaveDrop=world.LODOctaveDrop;
const int32 level=key.lod,extent=CHUNK_SIZE<<level; 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 FVoxelTileKey nativeKey=ToNative(key);
const FIntVector origin=nativeKey.OriginVoxels(); 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; TileMesh out;out.key=key;
out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution;
const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr; const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr;
const bool bNeedsFarShell=bHasWorldAssembly&&key.lod>=7 const bool bNeedsFarShell=bHasWorldAssembly&&key.lod>=7
&&(tileShape.HasFarWorld()||impl_->shape->HasFarWorld()); &&(tileShape.HasFarWorld()||impl_->shape->HasFarWorld());
@@ -691,6 +721,18 @@ namespace VoxelForgeCore
AppendFarStratumShells(world,farShape,nativeKey,step,mesh); AppendFarStratumShells(world,farShape,nativeKey,step,mesh);
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,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)); out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
if(bHasWorldAssembly) if(bHasWorldAssembly)
+351 -8
View File
@@ -16,6 +16,7 @@ namespace VoxelForgeCore
namespace world_assembly_detail namespace world_assembly_detail
{ {
constexpr std::int64_t Q16 = 1ll << 16; constexpr std::int64_t Q16 = 1ll << 16;
constexpr std::int64_t MinimumCliffShelfThicknessSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t MaxSerializedMagnitude = 1000000000000ll; constexpr std::int64_t MaxSerializedMagnitude = 1000000000000ll;
constexpr std::size_t MaxBiomeCount = 8; constexpr std::size_t MaxBiomeCount = 8;
constexpr std::size_t MaxStrateCount = 64; constexpr std::size_t MaxStrateCount = 64;
@@ -228,6 +229,60 @@ namespace world_assembly_detail
|| !std::all_of(point.positionSubvoxels.begin(), point.positionSubvoxels.end(), ValidScalar)) || !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; } { 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; 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.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); 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); HashWord(hash, stratum.route.size(), 4);
for (const auto& point : stratum.route) 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; } { error = "world assembly stratum has malformed optional evaluator-2 fields"; return false; }
if(hasWaterBoolean)stratum.waterEnabled=waterEnabled.Boolean(false); if(hasWaterBoolean)stratum.waterEnabled=waterEnabled.Boolean(false);
if(hasWaterNumber)stratum.waterEnabled=waterEnabled.Number()!=0.0; 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()) for (const auto& featureValue : item.Get("features").AsArray())
{ {
WorldAssemblyFeatureV1 feature; WorldAssemblyFeatureV1 feature;
@@ -1109,7 +1196,7 @@ namespace world_assembly_detail
inline std::int64_t CliffShelfDensity(const WorldAssemblyFeatureV1& feature, inline std::int64_t CliffShelfDensity(const WorldAssemblyFeatureV1& feature,
const WorldAssemblyRecipeV1& recipe,const WorldAssemblyStratumPlanV1& plan, const WorldAssemblyRecipeV1& recipe,const WorldAssemblyStratumPlanV1& plan,
std::int64_t x,std::int64_t y,std::int64_t z, 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; 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]) 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; return std::numeric_limits<std::int64_t>::max()/4;
const std::uint32_t vertical=SmoothQ16(static_cast<std::uint32_t>( const std::uint32_t vertical=SmoothQ16(static_cast<std::uint32_t>(
std::clamp<std::int64_t>(aboveGround*Q16/height,0,Q16))); 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 sourceX=x-dirX*sourceOffset;
const std::int64_t sourceY=y-dirY*sourceOffset; const std::int64_t sourceY=y-dirY*sourceOffset;
const HeightfieldPointSample source=CachedColumn(recipe,plan,sourceX,sourceY); const HeightfieldPointSample source=CachedColumn(recipe,plan,sourceX,sourceY);
if(source.groundHeightSubvoxels<=localColumn.groundHeightSubvoxels) if(source.groundHeightSubvoxels<=localColumn.groundHeightSubvoxels)
return std::numeric_limits<std::int64_t>::max()/4; 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); 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; return candidate;
} }
@@ -1192,6 +1298,26 @@ bool WorldAssemblyManifest::finalize(std::string& error)
for (auto& stratum : plan.strates) for (auto& stratum : plan.strates)
{ {
if (!world_assembly_detail::ValidateStratum(stratum, error)) return false; 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()) if (std::find(strateIds.begin(), strateIds.end(), stratum.strateIndex) != strateIds.end())
{ error = "world assembly plan contains a duplicate strate index"; return false; } { error = "world assembly plan contains a duplicate strate index"; return false; }
strateIds.push_back(stratum.strateIndex); strateIds.push_back(stratum.strateIndex);
@@ -1322,7 +1448,21 @@ std::string WorldAssemblyManifest::ToJson() const
<< feature.parametersSubvoxels[0] << ',' << feature.parametersSubvoxels[1] << ',' << feature.parametersSubvoxels[0] << ',' << feature.parametersSubvoxels[1] << ','
<< feature.parametersSubvoxels[2] << ',' << feature.parametersSubvoxels[3] << "]}"; << 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) for (std::size_t p = 0; p < stratum.route.size(); ++p)
{ {
const auto& point = stratum.route[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) 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, out.densitySubvoxels=SmoothMinSubvoxels(out.densitySubvoxels,shelf,
4*WorldAssemblySubvoxelsPerVoxel); 4*WorldAssemblySubvoxelsPerVoxel);
} }
@@ -1592,6 +1733,9 @@ FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept
(feature.maximumSubvoxels[2]-feature.minimumSubvoxels[2])/2})); (feature.maximumSubvoxels[2]-feature.minimumSubvoxels[2])/2}));
if(feature.type==4)maximum=std::max(maximum,feature.parametersSubvoxels[2] if(feature.type==4)maximum=std::max(maximum,feature.parametersSubvoxels[2]
+WorldAssemblySubvoxelsPerVoxel); +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])); if(feature.type==3)maximum=std::max(maximum,std::abs(feature.parametersSubvoxels[0]));
} }
out.maximumAbsoluteDisplacementSubvoxels = maximum; out.maximumAbsoluteDisplacementSubvoxels = maximum;
@@ -1746,4 +1890,203 @@ WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(const Heightfield2D& block
&& result.unknownBounds == 0 && result.negativeControlDetected; && result.unknownBounds == 0 && result.negativeControlDetected;
return result; 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
View File
@@ -1,7 +1,7 @@
# VoxelForge standalone world # VoxelForge standalone world
This C++20 program loads the owner snapshot from `data/owner_world.json`, runs the VoxelForge CPU 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 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. 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` `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 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`, manifest. The defaults use MSVC `/std:c++20`, `/O2`, `/arch:AVX2`, `/fp:precise`, `/MP16`, Normal
`/arch:AVX2`, `/fp:precise` and `/MP8`, BelowNormal priority and affinity `0xFF00`. Pass another priority and affinity `0xFFFF`. Use `-CompileWorkers 8 -BuildPriority BelowNormal` for a lower-impact
executable name to build beside any viewer that may be open by setting `-OutputName`, for example 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\`. `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 The standalone viewer accepts `--workers=1..16`, `--process-priority=Normal` and
CPUs. The viewer clamps worker requests to eight. `Build.ps1` bounds MSVC with `/MP8`, sets the `--affinity-all-cpu`. The verification runner in `Core\verification\RunMachine.ps1` retains its
build process to BelowNormal and affinity `0xFF00`, and retains `/fp:precise`. The verification separate BelowNormal/8-worker limits and refuses to overwrite an existing log stem.
runner in `Core\verification\RunMachine.ps1` applies the same limits to viewer runs and refuses to
overwrite an existing log stem.
## Controls ## 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; }
}
+234 -5
View File
@@ -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; 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, bool AttachRound1WorldAssemblyPrototype(FStandaloneOwnerWorld& owner,VFJson::Value& root,
std::string& error,const std::string& manifestOutputPath) 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); 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)); manifest.plan.strates.push_back(std::move(stratum));
if(!manifest.finalize(error))return false; if(!manifest.finalize(error))return false;
const std::string manifestText=manifest.ToJson(); 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()) if(!manifestOutputPath.empty())
{ {
const std::filesystem::path outputPath(manifestOutputPath); 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), static_cast<unsigned long long>(bounds.exceptionPointSamples),
bounds.negativeControlDetected?"true":"false",bounds.passed?"PASS":"FAIL"); 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; bool cliffRegressionPassing=false;
const auto cliffIt=std::find_if(plan.features.begin(),plan.features.end(), const auto cliffIt=std::find_if(plan.features.begin(),plan.features.end(),
[](const auto& feature){return feature.type==4;}); [](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)); coreQueryMaximumError=std::max(coreQueryMaximumError,std::abs(expected-actual));
++coreQuerySamples; ++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, coreCompiled?"true":"false",coreCompileMs,
static_cast<unsigned long long>(coreQuerySamples),coreQueryMaximumError, static_cast<unsigned long long>(coreQuerySamples),plan.route.size(),coreQueryMaximumError,
coreQueryPass?"field_ready_without_meshes":"unavailable",coreQueryPass?"PASS":"FAIL", coreQueryPass?"ready_without_meshes":"unavailable",coreQueryPass?"PASS":"FAIL",
coreError.empty()?"none":coreError.c_str()); coreError.empty()?"none":coreError.c_str());
if(!captureDirectory.empty()) 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"); ownerSaved&&nativeSaved?"PASS":"FAIL");
} }
std::printf("[WorldAssemblerFullViewTiming] status=NOT_MEASURED_IN_AUDIT_MODE; capture_viewer_runs_report_separately\n"); 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; &&deterministic&&coreQueryPass?0:11;
} }
} }