feat(composer): it invents strates that work -- 59/64, and §6.2 is CLOSED
The corpus was starved: the loader followed the active settings pool and
found ONE vector, so all 64 candidates were bit-identical and every number
was a placeholder. It now loads every UVoxelStrateDefinition via the Asset
Registry (Saved/Autosaves and Saved/Cooked excluded -- including them would
silently weight the corpus toward whatever was autosaved last) plus the
per-archetype defaults from VoxelStrateTypes.h, which are themselves
hand-tuned known-good vectors. Blending stays WITHIN an archetype;
different archetypes read different subsets of the 73 fields.
⭐ THE COMPOSER WORKS. 64 candidates blended from real parents, every one
measured:
survival 59/64 (92.2%)
non-vacuous 60 · largest-share >= .50 60 · primordial law 63 · roll
failures 0
and they are genuinely different worlds, not one world 64 times:
air fraction .0137 -> 1.000
walkable .000 -> .389
feature scale 4 -> 376 voxels
archetypes TunnelNetwork, VerticalShafts, FlatPlain, CrystalChamber,
SurfaceWorld, Underwater, FloatingIslands, Maze
⭐⭐ §6.2 IS CLOSED, and it was the real risk. Box-verdict bounds were only
ever proved against the parameters Jahni typed; the whole worry was that
invented parameters would violate a bound nobody had checked -- silently,
leaving no geometry and no collision. Against ROLLED parameters:
1,224 boxes proved (549 AllSolid, 675 AllAir), 1,336 Mixed
1,629,144 voxels brute-forced
0 VIOLATIONS
The bounds hold across the rolled range, not just at authored points. The
isolation work in 8e346a3 is what made that true.
The 5 rejections are a real finding, not noise: FloatingIslands can jitter
its islands out of existence and return an all-air strate (air 1.000,
walkable 0.000, feature scale 0). That is exactly what reject-and-resample
(§3.5) is for -- the measurement caught it without anyone anticipating it,
which is the whole argument for measuring rather than trusting.
Roll+measure 112.6 s for 64 candidates. §3.1's budget is "runs overnight",
so there is room for thousands.
Build clean, 23 succeeded / 0 warnings / 0 failed (the deliberate vacuity
warning is gone because the check is no longer vacuous). Verified by me.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
This commit is contained in:
1 parent
358bd415a9
commit
190ff381c0
5 files changed
+1518
-264
No files matched your search
+16
-11
@@ -438,16 +438,21 @@ One asset = one biome: identity + `DebugColor`, climate placement box (`ReliefMi
|
||||
atmosphere override, `WaterMaterial`, `MaterialPaletteIndex` (F6 — baked to vertex colour, §8.15)), `GameplayTags`. Referenced from
|
||||
`UVoxelStrateDefinition::Biomes[]`. Generator-agnostic (surface biomes now, cave biomes later). §8.14.
|
||||
|
||||
**`Public/VoxelStrateComposer.h` + `Private/VoxelStrateComposer.cpp`** (NEW, Tier 4a) — offline
|
||||
`FVoxelStrateCorpus` and `VF_RollStrateParams`. Walks the settings asset's fixed/pool definition
|
||||
references, admits only `TunnelNetwork` / `Underwater` vectors (the archetypes that author
|
||||
`GenerationParams`), measures every X-macro field's min/max/mean/population stddev, and exposes the
|
||||
explicit non-tunable list. Rolls are pure in `(corpus contents, seed, index)`: weighted 2–3-parent
|
||||
same-archetype selection → `FStrateGenerationParams::Lerp` → ±15% of measured field range jitter →
|
||||
editor-reflection clamps → ordered-pair repair. Bool `bTunnelsFlowTowardOrigin` inherits from the
|
||||
dominant parent; integer/enum SNAP fields are not jittered. This API is not called by runtime
|
||||
generation. The first project corpus run found one eligible `DA_Strate3` vector, so all measured
|
||||
spreads were zero; that limitation is recorded by the Tier 4a test/report rather than hidden.
|
||||
**`Public/VoxelStrateComposer.h` + `Private/VoxelStrateComposer.cpp`** (Tier 4a) — offline
|
||||
`FVoxelStrateCorpus` and `VF_RollStrateParams`. `LoadFromAssetRegistry` enumerates every project
|
||||
`UVoxelStrateDefinition` through the Asset Registry, excludes `Saved/Autosaves` and `Saved/Cooked`
|
||||
copies, and adds one `VoxelStrateTypes.h` default vector for each of the eight exact archetypes.
|
||||
The corpus is grouped by exact `ECaveGeneratorType`: the 2026-09-04 run loaded **4 project
|
||||
vectors + 8 defaults = 12 members** in **8 groups**. Sibling families use their native structs
|
||||
(`FSlabGenerationParams`, `FMazeGenerationParams`, `FSurfaceGenerationParams`,
|
||||
`FVerticalShaftParams`, `FFloatingIslandParams`); no cross-archetype blend is attempted.
|
||||
Spreads are measured per group, including the plain-native tunnel transport fields for reporting.
|
||||
Rolls are pure in `(corpus contents, seed, index)`: weighted 2–3-parent same-archetype selection →
|
||||
`FStrateGenerationParams::Lerp` or reflected native-family blend → ±15% of measured field range
|
||||
jitter → editor-reflection clamps → ordered-pair repair. Bool `bTunnelsFlowTowardOrigin` inherits
|
||||
from the dominant parent; integer/enum SNAP fields are not jittered. This API is not called by
|
||||
runtime generation. The settings audit found **one unique path**, `/Game/VoxelForge/DA_Strate3`,
|
||||
which explains the old one-vector corpus; the project assets were never in that pool.
|
||||
|
||||
### 3.9 Player edits — `Public/VoxelDiffLayer.h` + `.cpp`
|
||||
`UVoxelDiffLayer : UObject` (h:77). Stores `FVoxelModification` (h:43: Center/Radius/Strength;
|
||||
@@ -512,7 +517,7 @@ The plugin's first tests (`OPSTACK-PLAN.md` Phase 0.5). Run them from the editor
|
||||
| `VoxelForgeOpStackIslandTest.cpp` | `VoxelForge.OpStack.FloatingIslandEquivalence` | The port that runs the stack **backwards** — void + fill vs rock + carve, same classes with the opposite sign. Counts interior-solid and open-void samples separately (on this archetype an aggregate "N solid" is dominated by the seal bands and says nothing about the islands). Counts `AllSolid` and `AllAir` verdicts **separately** too: `AllAir` is the one no cave archetype could ever prove, and it is the entire perf argument here. |
|
||||
| `VoxelForgeOpStackMazeTest.cpp` | `VoxelForge.OpStack.MazeEquivalence` | **Phase 1's load-bearing test.** The 7-op Maze stack vs `GetMazeDensity` over 20k points (aiming for bit-identity; a side-of-iso disagreement is the hard fail), plus purity across workers and brute force on every box verdict the stack emits. Reports how many tiles the stack can prove uniform — today's `ClassifyTile` proves **zero** for any cave archetype. |
|
||||
| `VoxelForgeStrateParamCoverageTest.cpp` | `VoxelForge.Determinism.StrateParamBlendCoverage` | **The X-macro guard** (added 2026-08-17). `FStrateGenerationParams::Lerp` blends the hand-written `VF_STRATE_PARAM_FIELDS` list, **not** the struct — so a field added to one and not the other compiles, tests green, and silently takes its **default** inside every Gradient/Interleaved transition band. This expands the X-macro a **third** way (after LERP and SNAP), into a name list, and diffs it against the struct's UObject reflection. Pure shape test: no fixture, no world, instant. `GExemptFieldNames` is **empty** — every reflected field is covered today, and any exemption must be written down as a decision. Stakes rise with the world composer, which intends to invent parameter sets through this same `Lerp` (`COMPOSER-NOTES.md`). |
|
||||
| `VoxelForgeComposerParameterRollTest.cpp` | `VoxelForge.Composer.ParameterRoll` | Loads the hand-authored settings corpus, prints the complete per-field spread/exclusion/clamp table, asserts deterministic bit-identical rolls, measures 64 transient candidate strates with `VF_MeasureStrate` plus the exact unsnapped arrival→departure law, and brute-forces every rolled production box verdict. The first run found one eligible vector (all spreads zero), 64/64 survival, and a deliberately reported vacuous §6.2 scan: 2,560 Mixed, 0 proved, 0 violations. |
|
||||
| `VoxelForgeComposerParameterRollTest.cpp` | `VoxelForge.Composer.ParameterRoll` | Asset-Registry corpus audit + complete per-archetype spread/exclusion/clamp table; asserts bit-identical deterministic rerolls and same-archetype parents; measures 64 transient candidate strates with `VF_MeasureStrate` plus the exact unsnapped arrival→departure law; brute-forces every rolled production box verdict. The final full-suite run: **59/64 survival (92.2%)**, **1,336 Mixed + 549 AllSolid + 675 AllAir = 1,224 proved boxes**, **1,629,144 lattice voxels checked, 0 violations**, 112.574 s total. |
|
||||
| `VoxelForgeLayoutOrderIndependenceTest.cpp` | `VoxelForge.Determinism.LayoutOrderIndependence` | Builds a known transient soft-pointer pool, then rebuilds it in original, reversed, and swapped orders. Requires a non-empty layout and passage set, and compares every slot's definition/Z/height plus passage endpoints, radius, type, control geometry, and bounds bit-for-bit. |
|
||||
| `VoxelForgePassageOpenSpaceTest.cpp` | `VoxelForge.Determinism.PassageLandsInOpenSpace` | Uses the real fixture density path to check every generated inter-strate passage whose destination query answers: a 16-point ring outside the mouth's carve/blend band has at least half its samples in destination air, and the endpoint matches the pure open-point result within the mouth's float envelope. This is a connectivity proxy, not a flood-fill proof. Reports checked passages and false/unanswerable archetypes; fails if it inspects zero passages. |
|
||||
| `VoxelForgeStrateConnectivityTest.cpp` | `VoxelForge.Generation.StrateConnectivity` / `VoxelForge.Generation.StrateConnectivityRefinement` / `VoxelForge.Generation.VerticalShaftSeamFreedom` | Bounded strate metrics with density-polarity and solid-gap controls, deterministic route rechecks, and refinement sweeps. The refinement test fits the measurement AABB to each arrival/departure mouth pair, reports exact before/after seed-6 cell counts, reruns every negative with doubled margin, measures tree orphan candidates/path reachability and roughness-bubble proxies, asserts axis landings, checks source/mirror physical paths, and requires 16/16 effective arrival→departure results across the 16 VerticalShafts seeds. Margin-binding negatives are reported as measurement limits, never as gap findings. The seam test re-evaluates cell-boundary positions after warming distinct neighbouring chunk contexts in both legacy and operator-stack paths. |
|
||||
|
||||
+91
-20
@@ -200,18 +200,66 @@ exclusion list. Bools cannot be blended — roll them by probability or inherit
|
||||
|
||||
`Public/VoxelStrateComposer.h` + `Private/VoxelStrateComposer.cpp` now implement the corpus and
|
||||
the deterministic roll. `Private/Tests/VoxelForgeComposerParameterRollTest.cpp` is the offline
|
||||
measurement harness. The roll uses `FStrateGenerationParams::Lerp`, chooses 2–3 weighted parents
|
||||
inside one archetype group, applies jitter of **±15% of each field's measured corpus range
|
||||
(`max-min`)**, then applies reflected `ClampMin`/`ClampMax` metadata. It does not use clamps as
|
||||
distribution ranges, does not touch generation, and leaves `WorldRadiusVoxels` at its default 0.
|
||||
measurement harness. The roll uses the native family blend (`FStrateGenerationParams::Lerp` for
|
||||
the tunnel family), chooses 2–3 weighted parents **inside one exact archetype group**, applies
|
||||
jitter of **±15% of each field's measured corpus range (`max-min`)**, then applies reflected
|
||||
`ClampMin`/`ClampMax` metadata. It does not use clamps as distribution ranges, does not touch
|
||||
generation, and leaves `WorldRadiusVoxels` at its default 0. The sibling native parameter structs
|
||||
are carried in the same corpus entry, so a `SurfaceWorld` vector is never blended with a
|
||||
`TunnelNetwork` vector merely because both happen to be stored in a strate asset.
|
||||
|
||||
The loader walks the settings asset's fixed-strate and pool references, de-duplicates by asset path,
|
||||
and accepts only `TunnelNetwork` / `Underwater`: those are the only archetypes whose authored vector
|
||||
is `GenerationParams`. The current `DA_Settings` asset contains **one unique resolved reference**,
|
||||
`DA_Strate3`, and it is `TunnelNetwork`; the other authored archetype assets are not in that
|
||||
settings corpus. Consequently every measured field has zero spread, all 64 rolls repeat the same
|
||||
known-good vector (parent weights still roll deterministically), and this run did not exercise
|
||||
non-zero jitter. That is a corpus-size finding, not a rate optimization.
|
||||
The original one-vector result was caused by the old loader following `DA_Settings`' fixed-strate
|
||||
and pool references. An audit of those references finds **one unique path**,
|
||||
`/Game/VoxelForge/DA_Strate3.DA_Strate3`, whose archetype is `TunnelNetwork`; that is why the
|
||||
previous corpus only loaded one asset. The current loader instead asks the Asset Registry for every
|
||||
project `UVoxelStrateDefinition`, sorts and de-duplicates the results, accepts `/Game` packages,
|
||||
and ignores `Saved/Autosaves` and `Saved/Cooked` copies. The scan found **4 project assets**, with
|
||||
no saved-copy, outside-project, duplicate, or unresolved records:
|
||||
|
||||
| Asset | Archetype |
|
||||
|---|---|
|
||||
| `DA_Strate1` | `SurfaceWorld` |
|
||||
| `DA_Strate2` | `FloatingIslands` |
|
||||
| `DA_Strate3` | `TunnelNetwork` |
|
||||
| `DA_Strate4` | `TunnelNetwork` |
|
||||
|
||||
The hand-authored C++ defaults in `VoxelStrateTypes.h` are also corpus members: one for each of the
|
||||
8 supported archetypes (`TunnelNetwork`, `FlatPlain`, `CrystalChamber`, `Maze`, `SurfaceWorld`,
|
||||
`VerticalShafts`, `FloatingIslands`, `Underwater`). The fed corpus is therefore **12 members** in
|
||||
**8 exact-archetype groups**. Group membership is `TunnelNetwork=3`, `FlatPlain=1`,
|
||||
`CrystalChamber=1`, `Maze=1`, `SurfaceWorld=2`, `VerticalShafts=1`, `FloatingIslands=2`, and
|
||||
`Underwater=1`.
|
||||
|
||||
The complete 253-descriptor spread table is emitted by the automation test, including sample count,
|
||||
min/max/mean/standard deviation, excluded/reflected status, and clamp metadata. The 230 zero-range
|
||||
rows are intentionally omitted below; these are the **23 rows with real spread** (float formatting
|
||||
is the test's measured output):
|
||||
|
||||
| Archetype | Field | N | Min | Max | Mean | StdDev |
|
||||
|---|---|---:|---:|---:|---:|---:|
|
||||
| TunnelNetwork | MinRoomRadius | 3 | 10 | 25 | 15 | 7.07106781 |
|
||||
| TunnelNetwork | MaxRoomRadius | 3 | 30 | 125 | 61.6666667 | 44.7834295 |
|
||||
| TunnelNetwork | OriginRoomRadius | 3 | 20 | 120 | 53.3333333 | 47.1404521 |
|
||||
| TunnelNetwork | TunnelMinRadius | 3 | 3 | 9 | 5 | 2.82842712 |
|
||||
| TunnelNetwork | TunnelMaxRadius | 3 | 7 | 15 | 9.66666667 | 3.77123617 |
|
||||
| SurfaceWorld | BaseGroundRelative | 2 | 0.200000003 | 0.300000012 | 0.250000007 | 0.0500000045 |
|
||||
| SurfaceWorld | ElevationRange | 2 | 60 | 200 | 130 | 70 |
|
||||
| SurfaceWorld | ContinentFrequency | 2 | 0.00100000005 | 0.00600000005 | 0.00350000005 | 0.0025 |
|
||||
| SurfaceWorld | MountainStrength | 2 | 0.5 | 1 | 0.75 | 0.25 |
|
||||
| SurfaceWorld | MountainFrequency | 2 | 0.00200000009 | 0.0120000001 | 0.0070000001 | 0.005 |
|
||||
| SurfaceWorld | SurfaceRoughness | 2 | 2 | 3 | 2.5 | 0.5 |
|
||||
| SurfaceWorld | ReliefStrength | 2 | 0 | 0.699999988 | 0.349999994 | 0.349999994 |
|
||||
| SurfaceWorld | ReliefContrast | 2 | 1.60000002 | 2 | 1.80000001 | 0.199999988 |
|
||||
| SurfaceWorld | TerraceHeight | 2 | 12 | 20 | 16 | 4 |
|
||||
| SurfaceWorld | BeachWidth | 2 | 8 | 200 | 104 | 96 |
|
||||
| FloatingIslands | IslandSpacing | 2 | 95 | 1000 | 547.5 | 452.5 |
|
||||
| FloatingIslands | IslandDensity | 2 | 0.100000001 | 0.5 | 0.300000001 | 0.199999999 |
|
||||
| FloatingIslands | IslandMinRadius | 2 | 18 | 50 | 34 | 16 |
|
||||
| FloatingIslands | IslandMaxRadius | 2 | 42 | 500 | 271 | 229 |
|
||||
| FloatingIslands | ThicknessRatio | 2 | 0.100000001 | 0.699999988 | 0.399999995 | 0.299999993 |
|
||||
| FloatingIslands | VerticalJitter | 2 | 0.200000003 | 0.600000024 | 0.400000013 | 0.20000001 |
|
||||
| FloatingIslands | TopFlatten | 2 | 0.600000024 | 0.800000012 | 0.700000018 | 0.099999994 |
|
||||
| FloatingIslands | SurfaceRoughness | 2 | 0 | 4 | 2 | 2 |
|
||||
|
||||
The reflection/use audit found **36 excluded fields**: the 34 terrain-op transport fields
|
||||
`TerraceStepHeight`, `TerraceHardness`, `TerraceNoiseDisplacement`, `LayerLineSpacing`,
|
||||
@@ -222,22 +270,45 @@ The reflection/use audit found **36 excluded fields**: the 34 terrain-op transpo
|
||||
`ChimneyMaxRadius`, `ChimneyHeight`, `DomeDensity`, `DomeMinRadius`, `DomeMaxRadius`,
|
||||
`DomeHeightRatio`, `PinchDensity`, `PinchStrength`, `PinchLength` — populated per room by
|
||||
`UVoxelTerrainOpDefinition` — plus the manager-owned runtime bounds `StrateTopWorldZ` and
|
||||
`StrateBottomWorldZ`. The test prints the complete field-by-field table, including excluded fields,
|
||||
`StrateBottomWorldZ`. The tunnel transport fields are measured directly from the native
|
||||
`FStrateGenerationParams` X-macro even though they are not reflected `UPROPERTY`s; their values are
|
||||
reported but deliberately not jittered. Same-named fields in sibling structs are not automatically
|
||||
excluded: the exclusion applies to the tunnel transport slots whose generation source is the
|
||||
terrain-op pool. The test prints the complete field-by-field table, including excluded fields,
|
||||
reflection status, and clamp metadata.
|
||||
|
||||
The terrain-field source is now traced in code. `VoxelStrateManager.cpp` loads the active strate's
|
||||
`TerrainOperations` soft-object pool, and `VoxelGenerator.cpp` passes that pool to
|
||||
`VoxelCaveMorphology::BuildChunkCache`. For each nearest room, `FVoxelTerrainOpDefinition::ApplyTo`
|
||||
overlays the selected operation onto a local copy (`FStrateGenerationParams LocalTerrainParams =
|
||||
Params`) before the room operator runs. Thus the 34 values are transported through the strate
|
||||
params, but their authored cave-detail values come from `UVoxelTerrainOpDefinition` assets selected
|
||||
by the active strate's terrain-operation pool. Rolling those slots in the strate composer is not a
|
||||
useful way to roll cave detail: selected room operations overwrite them. It is not a literal
|
||||
absolute no-op, because the local base copy is still used where no operation overwrites a slot.
|
||||
The correct future design is a **second corpus** over terrain-op assets plus the active pool's
|
||||
membership/weights; this task does not build it. The project currently has **1**
|
||||
`UVoxelTerrainOpDefinition` asset (the Asset Registry count used by the test).
|
||||
|
||||
The bool policy is explicit: `bTunnelsFlowTowardOrigin` is inherited from the dominant parent.
|
||||
`OriginRoomMaxConnections` and `RoughnessNoiseType` retain `Lerp`'s existing SNAP behavior and
|
||||
are not jittered. In this editor build clamp metadata was available while the table was built; it
|
||||
is **not promised in a cooked runtime**, so the commandlet/cook-time bake remains owed.
|
||||
|
||||
The first 64-candidate run (step 4, radius 256, max 8,000,000 cells, fixture seed 1337) produced
|
||||
64/64 non-vacuous candidates, 64/64 largest-component share ≥ 0.50, and 64/64 exact unsnapped
|
||||
arrival→departure law passes. Every row was `air=0.520272`, `largest share=0.999996`,
|
||||
`walkable=0.057995`, `feature scale=20.000000`, `Connected`; total runtime was **542.220 s**.
|
||||
The §6.2 check saw **2,560 Mixed, 0 AllSolid, 0 AllAir, 0 brute-force voxels, 0 violations**:
|
||||
this is explicitly **vacuous**, not evidence that rolled bounds are sound, because the current cave
|
||||
box path emitted no uniform verdict. The test leaves §6.2 open and reports that fact rather than
|
||||
turning it into a workaround.
|
||||
The fed 64-candidate run (step 4 sweep, radius 256, max 8,000,000 cells, fixture seed 1337)
|
||||
produced **59/64 survivors (92.2%)**. There were 60 non-vacuous candidates, 60 with largest
|
||||
component share ≥ 0.50, 63 exact unsnapped arrival→departure law passes, and no roll failures.
|
||||
The complete 64-row table is emitted by `VoxelForge.Composer.ParameterRoll`; each row reports
|
||||
archetype, weighted parents, air fraction, largest share, walkable fraction, feature scale, and
|
||||
arrival→departure verdict. The final full-suite run took **112.574 s** total (**0.015 s** corpus
|
||||
load, **112.556 s** roll/measurement).
|
||||
|
||||
The §6.2 check is no longer vacuous: **1,224 boxes proved**, **1,629,144 voxels checked**,
|
||||
**0 violations**. Its verdict mix was 1,336 Mixed, 549 AllSolid, and 675 AllAir; the law check
|
||||
reported no violating candidate. The box law used unit step and brute-forced every voxel of each
|
||||
uniform box, while the candidate sweep retained step 4. The test asserts zero violations and keeps
|
||||
the law open to future changed candidates rather than treating this clean run as a license to tune
|
||||
the survival rate.
|
||||
|
||||
### 3.4 The measurement pass — one grid, one flood fill, three jobs
|
||||
|
||||
|
||||
@@ -4,10 +4,15 @@
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "HAL/PlatformTime.h"
|
||||
#include "AssetRegistry/AssetData.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
#include "VoxelForgeTestFixture.h"
|
||||
#include "VoxelSettings.h"
|
||||
#include "VoxelStrateComposer.h"
|
||||
#include "VoxelStrateMeasure.h"
|
||||
#include "VoxelTerrainOpDefinition.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -53,6 +58,124 @@ namespace
|
||||
*VF_FormatSpreadValue(Spread.ClampMax));
|
||||
}
|
||||
|
||||
bool VF_IsProjectPackagePath(const FString& PackagePath)
|
||||
{
|
||||
return PackagePath == TEXT("/Game")
|
||||
|| PackagePath.StartsWith(TEXT("/Game/"), ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
bool VF_IsIgnoredSavedCopy(const FString& PackagePath)
|
||||
{
|
||||
return PackagePath.Contains(TEXT("/Saved/Autosaves"), ESearchCase::IgnoreCase)
|
||||
|| PackagePath.Contains(TEXT("/Saved/Cooked"), ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
TArray<FString> VF_GetProjectAssetPaths(UClass* AssetClass)
|
||||
{
|
||||
TArray<FString> Paths;
|
||||
if (AssetClass == nullptr)
|
||||
{
|
||||
return Paths;
|
||||
}
|
||||
|
||||
FAssetRegistryModule& AssetRegistryModule =
|
||||
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
||||
TArray<FAssetData> Assets;
|
||||
AssetRegistryModule.Get().GetAssetsByClass(AssetClass->GetClassPathName(), Assets, true);
|
||||
|
||||
TSet<FString> SeenPaths;
|
||||
for (const FAssetData& Asset : Assets)
|
||||
{
|
||||
const FString PackagePath = Asset.PackageName.ToString();
|
||||
const FString ObjectPath = Asset.GetObjectPathString();
|
||||
if (!VF_IsProjectPackagePath(PackagePath)
|
||||
|| VF_IsIgnoredSavedCopy(PackagePath)
|
||||
|| VF_IsIgnoredSavedCopy(ObjectPath)
|
||||
|| SeenPaths.Contains(ObjectPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SeenPaths.Add(ObjectPath);
|
||||
Paths.Add(ObjectPath);
|
||||
}
|
||||
Paths.Sort();
|
||||
return Paths;
|
||||
}
|
||||
|
||||
FString VF_FormatSettingsAudit(const UVoxelSettings* Settings)
|
||||
{
|
||||
TArray<FString> Paths;
|
||||
if (Settings != nullptr)
|
||||
{
|
||||
TSet<FString> UniquePaths;
|
||||
for (const TPair<int32, TSoftObjectPtr<UVoxelStrateDefinition>>& Pair : Settings->FixedStrates)
|
||||
{
|
||||
const FString Path = Pair.Value.ToSoftObjectPath().ToString();
|
||||
if (!Path.IsEmpty())
|
||||
{
|
||||
UniquePaths.Add(Path);
|
||||
}
|
||||
}
|
||||
for (const TSoftObjectPtr<UVoxelStrateDefinition>& Reference : Settings->StratePool)
|
||||
{
|
||||
const FString Path = Reference.ToSoftObjectPath().ToString();
|
||||
if (!Path.IsEmpty())
|
||||
{
|
||||
UniquePaths.Add(Path);
|
||||
}
|
||||
}
|
||||
for (const FString& Path : UniquePaths)
|
||||
{
|
||||
Paths.Add(Path);
|
||||
}
|
||||
Paths.Sort();
|
||||
}
|
||||
|
||||
FString Result = FString::Printf(TEXT("Settings corpus audit: %d unique fixed/pool paths"),
|
||||
Paths.Num());
|
||||
for (const FString& Path : Paths)
|
||||
{
|
||||
Result += FString::Printf(TEXT(" [%s]"), *Path);
|
||||
}
|
||||
Result += TEXT("; these settings references are not the corpus source.");
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool VF_RollRuntimeZIsZero(const FVoxelStrateRollInfo& Roll)
|
||||
{
|
||||
switch (Roll.Archetype)
|
||||
{
|
||||
case ECaveGeneratorType::TunnelNetwork:
|
||||
case ECaveGeneratorType::Underwater:
|
||||
return Roll.ArchetypeParams.TunnelNetworkParams.StrateTopWorldZ == 0.0f
|
||||
&& Roll.ArchetypeParams.TunnelNetworkParams.StrateBottomWorldZ == 0.0f;
|
||||
case ECaveGeneratorType::FlatPlain:
|
||||
case ECaveGeneratorType::CrystalChamber:
|
||||
return Roll.ArchetypeParams.SlabParams.StrateTopWorldZ == 0.0f
|
||||
&& Roll.ArchetypeParams.SlabParams.StrateBottomWorldZ == 0.0f;
|
||||
case ECaveGeneratorType::Maze:
|
||||
return Roll.ArchetypeParams.MazeParams.StrateTopWorldZ == 0.0f
|
||||
&& Roll.ArchetypeParams.MazeParams.StrateBottomWorldZ == 0.0f;
|
||||
case ECaveGeneratorType::SurfaceWorld:
|
||||
return Roll.ArchetypeParams.SurfaceParams.StrateTopWorldZ == 0.0f
|
||||
&& Roll.ArchetypeParams.SurfaceParams.StrateBottomWorldZ == 0.0f;
|
||||
case ECaveGeneratorType::VerticalShafts:
|
||||
return Roll.ArchetypeParams.VerticalShaftParams.StrateTopWorldZ == 0.0f
|
||||
&& Roll.ArchetypeParams.VerticalShaftParams.StrateBottomWorldZ == 0.0f;
|
||||
case ECaveGeneratorType::FloatingIslands:
|
||||
return Roll.ArchetypeParams.FloatingIslandParams.StrateTopWorldZ == 0.0f
|
||||
&& Roll.ArchetypeParams.FloatingIslandParams.StrateBottomWorldZ == 0.0f;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool VF_IsComposerDefault(const FVoxelStrateCorpusEntry& Entry)
|
||||
{
|
||||
return Entry.SourcePath.StartsWith(TEXT("/VoxelForge/ComposerDefaults/"),
|
||||
ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
FString VF_FormatParents(const FVoxelStrateCorpus& Corpus,
|
||||
const FVoxelStrateRollInfo& Roll)
|
||||
{
|
||||
@@ -233,21 +356,81 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
FVoxelStrateCorpus Corpus;
|
||||
FString CorpusReport;
|
||||
const double CorpusStartSeconds = FPlatformTime::Seconds();
|
||||
const bool bCorpusLoaded = Corpus.LoadFromSettings(AuthoredSettings, CorpusReport);
|
||||
const bool bCorpusLoaded = Corpus.LoadFromAssetRegistry(CorpusReport);
|
||||
const double CorpusSeconds = FPlatformTime::Seconds() - CorpusStartSeconds;
|
||||
AddInfo(FString::Printf(TEXT("%s Load time %.3fs; contents hash 0x%08x; clamp metadata at "
|
||||
"build=%s, promised at cooked runtime=%s."),
|
||||
*CorpusReport, CorpusSeconds, Corpus.GetContentsHash(),
|
||||
Corpus.AreClampMetadataAvailableAtBuild() ? TEXT("yes") : TEXT("no"),
|
||||
Corpus.AreClampMetadataAvailableAtRuntime() ? TEXT("yes") : TEXT("no")));
|
||||
AddInfo(VF_FormatSettingsAudit(AuthoredSettings));
|
||||
|
||||
const TArray<FString> ProjectStrateAssets =
|
||||
VF_GetProjectAssetPaths(UVoxelStrateDefinition::StaticClass());
|
||||
const TArray<FString> ProjectTerrainOpAssets =
|
||||
VF_GetProjectAssetPaths(UVoxelTerrainOpDefinition::StaticClass());
|
||||
int32 NumProjectCorpusEntries = 0;
|
||||
int32 NumDefaultCorpusEntries = 0;
|
||||
FString CorpusMembership = TEXT("CORPUS MEMBERSHIP\n");
|
||||
for (const FVoxelStrateCorpusEntry& Entry : Corpus.GetEntries())
|
||||
{
|
||||
if (VF_IsComposerDefault(Entry))
|
||||
{
|
||||
++NumDefaultCorpusEntries;
|
||||
}
|
||||
else
|
||||
{
|
||||
++NumProjectCorpusEntries;
|
||||
}
|
||||
CorpusMembership += FString::Printf(TEXT("%s | %s | %s\n"),
|
||||
VF_IsComposerDefault(Entry) ? TEXT("default") : TEXT("project"),
|
||||
VF_GetStrateArchetypeName(Entry.Archetype),
|
||||
*Entry.SourcePath);
|
||||
}
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Corpus membership: %d project strate assets discovered, %d project vectors loaded, "
|
||||
"%d defaults, %d total members. Terrain-op Asset Registry count (not rolled in this "
|
||||
"task): %d."),
|
||||
ProjectStrateAssets.Num(), NumProjectCorpusEntries, NumDefaultCorpusEntries, Corpus.Num(),
|
||||
ProjectTerrainOpAssets.Num()));
|
||||
AddInfo(CorpusMembership);
|
||||
for (const ECaveGeneratorType Archetype : {
|
||||
ECaveGeneratorType::TunnelNetwork,
|
||||
ECaveGeneratorType::FlatPlain,
|
||||
ECaveGeneratorType::CrystalChamber,
|
||||
ECaveGeneratorType::Maze,
|
||||
ECaveGeneratorType::SurfaceWorld,
|
||||
ECaveGeneratorType::VerticalShafts,
|
||||
ECaveGeneratorType::FloatingIslands,
|
||||
ECaveGeneratorType::Underwater})
|
||||
{
|
||||
int32 NumDefaultsForArchetype = 0;
|
||||
for (const FVoxelStrateCorpusEntry& Entry : Corpus.GetEntries())
|
||||
{
|
||||
if (Entry.Archetype == Archetype && VF_IsComposerDefault(Entry))
|
||||
{
|
||||
++NumDefaultsForArchetype;
|
||||
}
|
||||
}
|
||||
TestEqual(FString::Printf(TEXT("one hand-tuned default for %s"),
|
||||
VF_GetStrateArchetypeName(Archetype)),
|
||||
NumDefaultsForArchetype, 1);
|
||||
TestTrue(FString::Printf(TEXT("%s has a same-archetype corpus group"),
|
||||
VF_GetStrateArchetypeName(Archetype)),
|
||||
Corpus.NumForArchetype(Archetype) > 0);
|
||||
}
|
||||
TestEqual(TEXT("all project strate assets plus eight defaults are corpus members"),
|
||||
Corpus.Num(), ProjectStrateAssets.Num() + 8);
|
||||
|
||||
FString SpreadTable = TEXT(
|
||||
"CORPUS SPREAD (population stddev; jitter range = ±15% of max-min; clamps are safety only)\n"
|
||||
"field | kind | excluded | reflected | samples | min | max | mean | stddev | clamp\n");
|
||||
"archetype | struct | field | kind | excluded | reflected | samples | min | max | mean | stddev | clamp\n");
|
||||
for (const FVoxelStrateFieldSpread& Spread : Corpus.GetFieldSpreads())
|
||||
{
|
||||
SpreadTable += FString::Printf(
|
||||
TEXT("%s | %s | %s | %s | %d | %s | %s | %s | %s | %s\n"),
|
||||
TEXT("%s | %s | %s | %s | %s | %s | %d | %s | %s | %s | %s | %s\n"),
|
||||
VF_GetStrateArchetypeName(Spread.Archetype),
|
||||
*Spread.ParamStructName,
|
||||
*Spread.FieldName,
|
||||
VF_FieldKindName(Spread.Kind),
|
||||
Spread.bExcluded ? TEXT("yes") : TEXT("no"),
|
||||
@@ -270,9 +453,10 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
AddInfo(ExclusionTable);
|
||||
AddInfo(TEXT(
|
||||
"Bool policy: bTunnelsFlowTowardOrigin is inherited from the dominant weighted parent; "
|
||||
"int/enum SNAP fields use FStrateGenerationParams::Lerp and are not jittered. Parent "
|
||||
"selection is weight-1.0 (equal) because UVoxelStrateDefinition has no corpus-weight field; "
|
||||
"parents are selected only within one archetype group."));
|
||||
"int/enum SNAP fields use the native family blend and are not jittered. Parent selection "
|
||||
"is weight-1.0 (equal) because UVoxelStrateDefinition has no corpus-weight field; parents "
|
||||
"are selected only within one exact archetype group. FStrateGenerationParams terrain-op "
|
||||
"transport fields remain excluded; the sibling families' authored fields are rolled."));
|
||||
|
||||
TestTrue(TEXT("the reflected parameter schema is covered or explicitly excluded"),
|
||||
Corpus.IsSchemaValid());
|
||||
@@ -281,7 +465,7 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("Cannot run the 64-candidate measurement: %s"),
|
||||
Corpus.IsSchemaValid() ? TEXT("the settings asset exposed no TunnelNetwork/Underwater GenerationParams")
|
||||
Corpus.IsSchemaValid() ? TEXT("the Asset Registry exposed no usable strate vectors")
|
||||
: *Corpus.GetSchemaError()));
|
||||
return false;
|
||||
}
|
||||
@@ -326,6 +510,7 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
int64 TotalBoxCheckedVoxels = 0;
|
||||
int32 TotalBoxViolations = 0;
|
||||
TArray<int32> BoxViolationCandidates;
|
||||
FString FirstBoxViolation;
|
||||
|
||||
const double RollAndMeasureStartSeconds = FPlatformTime::Seconds();
|
||||
for (int32 CandidateIndex = 0; CandidateIndex < NumCandidates; ++CandidateIndex)
|
||||
@@ -349,23 +534,47 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
continue;
|
||||
}
|
||||
|
||||
bool bParentsMatchArchetype = true;
|
||||
for (const int32 ParentIndex : Roll.ParentEntryIndices)
|
||||
{
|
||||
if (!Corpus.GetEntries().IsValidIndex(ParentIndex)
|
||||
|| Corpus.GetEntries()[ParentIndex].Archetype != Roll.Archetype)
|
||||
{
|
||||
bParentsMatchArchetype = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
TestTrue(FString::Printf(TEXT("candidate %d parents stay within %s"),
|
||||
CandidateIndex, VF_GetStrateArchetypeName(Roll.Archetype)),
|
||||
bParentsMatchArchetype);
|
||||
|
||||
const FVoxelStrateRollInfo RepeatRoll = VF_RollStrateParamsDetailed(
|
||||
Corpus, AuthoredSettings->Seed, CandidateIndex);
|
||||
TestTrue(FString::Printf(TEXT("candidate %d is bit-identical on deterministic reroll"),
|
||||
CandidateIndex),
|
||||
RepeatRoll.bValid && RepeatRoll.Archetype == Roll.Archetype
|
||||
&& VF_AreStrateArchetypeParamsBitIdentical(
|
||||
RepeatRoll.ArchetypeParams, Roll.ArchetypeParams, Roll.Archetype)
|
||||
&& RepeatRoll.ParentEntryIndices == Roll.ParentEntryIndices
|
||||
&& RepeatRoll.ParentWeights == Roll.ParentWeights);
|
||||
|
||||
// Put the invented vector into a transient fixture definition. This is an offline test
|
||||
// world only; the runtime manager/generation code and authored assets are untouched.
|
||||
UVoxelStrateDefinition* CandidateDefinition =
|
||||
World.Definitions[CandidateStrateIndex].Get();
|
||||
CandidateDefinition->GeneratorType = Roll.Archetype;
|
||||
CandidateDefinition->GenerationParams = Roll.Params;
|
||||
CandidateDefinition->GenerationParams = Roll.ArchetypeParams.TunnelNetworkParams;
|
||||
CandidateDefinition->SlabParams = Roll.ArchetypeParams.SlabParams;
|
||||
CandidateDefinition->MazeParams = Roll.ArchetypeParams.MazeParams;
|
||||
CandidateDefinition->SurfaceParams = Roll.ArchetypeParams.SurfaceParams;
|
||||
CandidateDefinition->VerticalShaftParams = Roll.ArchetypeParams.VerticalShaftParams;
|
||||
CandidateDefinition->FloatingIslandParams = Roll.ArchetypeParams.FloatingIslandParams;
|
||||
CandidateDefinition->bUseOperatorStack = true;
|
||||
CandidateDefinition->TransitionType = EVoxelStrateTransition::Hard;
|
||||
World.Reinitialize();
|
||||
|
||||
const FStrateGenerationParams RuntimeParams = World.StrateManager->GetGenerationParams(
|
||||
FIntVector(0, 0,
|
||||
World.StrateManager->GetLayout()[CandidateStrateIndex].BottomChunkZ
|
||||
+ (World.StrateManager->GetLayout()[CandidateStrateIndex].TopChunkZ
|
||||
- World.StrateManager->GetLayout()[CandidateStrateIndex].BottomChunkZ) / 2));
|
||||
TestTrue(FString::Printf(TEXT("candidate %d keeps runtime Z out of the rolled vector"), CandidateIndex),
|
||||
Roll.Params.StrateTopWorldZ == 0.0f && Roll.Params.StrateBottomWorldZ == 0.0f);
|
||||
VF_RollRuntimeZIsZero(Roll));
|
||||
|
||||
const FVoxelStrateMetrics Metrics = VF_MeasureStrate(
|
||||
*World.Generator, *World.StrateManager, CandidateStrateIndex, MeasureSettings);
|
||||
@@ -438,6 +647,10 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
if (BoxReport.Violations > 0)
|
||||
{
|
||||
BoxViolationCandidates.Add(CandidateIndex);
|
||||
if (FirstBoxViolation.IsEmpty())
|
||||
{
|
||||
FirstBoxViolation = BoxReport.FirstViolation;
|
||||
}
|
||||
}
|
||||
|
||||
CandidateTable += FString::Printf(
|
||||
@@ -481,8 +694,8 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
AddError(FString::Printf(
|
||||
TEXT("ROLLED BOX-VERDICT VIOLATION: %d candidate(s) produced a false AllSolid/AllAir "
|
||||
"claim; first candidate index %d. This closes the test red because §6.2 bounds "
|
||||
"were not sound for the rolled parameter space."),
|
||||
BoxViolationCandidates.Num(), BoxViolationCandidates[0]));
|
||||
"were not sound for the rolled parameter space. First violation: %s"),
|
||||
BoxViolationCandidates.Num(), BoxViolationCandidates[0], *FirstBoxViolation));
|
||||
}
|
||||
if (TotalBoxProved == 0)
|
||||
{
|
||||
@@ -504,4 +717,3 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
||||
}
|
||||
|
||||
#endif // WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -28,9 +28,11 @@ struct VOXELFORGE_API FVoxelStrateFieldExclusion
|
||||
FString Reason;
|
||||
};
|
||||
|
||||
/** Measured statistics for one field across the currently loaded corpus. */
|
||||
/** Measured statistics for one field in one archetype's corpus group. */
|
||||
struct VOXELFORGE_API FVoxelStrateFieldSpread
|
||||
{
|
||||
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
|
||||
FString ParamStructName;
|
||||
FString FieldName;
|
||||
EVoxelStrateFieldKind Kind = EVoxelStrateFieldKind::Continuous;
|
||||
bool bExcluded = false;
|
||||
@@ -50,12 +52,30 @@ struct VOXELFORGE_API FVoxelStrateFieldSpread
|
||||
double ClampMax = 0.0;
|
||||
};
|
||||
|
||||
/** Native parameter storage for every currently supported strate archetype. */
|
||||
struct VOXELFORGE_API FVoxelStrateArchetypeParams
|
||||
{
|
||||
FStrateGenerationParams TunnelNetworkParams;
|
||||
FSlabGenerationParams SlabParams;
|
||||
FMazeGenerationParams MazeParams;
|
||||
FSurfaceGenerationParams SurfaceParams;
|
||||
FVerticalShaftParams VerticalShaftParams;
|
||||
FFloatingIslandParams FloatingIslandParams;
|
||||
};
|
||||
|
||||
/** One known-good authored vector and the archetype that gives it meaning. */
|
||||
struct VOXELFORGE_API FVoxelStrateCorpusEntry
|
||||
{
|
||||
FString SourcePath;
|
||||
FString SourceName;
|
||||
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
|
||||
|
||||
// Native storage is selected by Archetype. Keeping every family here makes the corpus
|
||||
// type-safe without changing any runtime generation parameter struct.
|
||||
FVoxelStrateArchetypeParams ArchetypeParams;
|
||||
|
||||
// Compatibility view for callers of the original Tier 4a API. It is meaningful for
|
||||
// TunnelNetwork/Underwater entries and is the same value as ArchetypeParams.TunnelNetworkParams.
|
||||
FStrateGenerationParams Params;
|
||||
|
||||
// The current asset format has no corpus weight field. Loaded hand-authored entries therefore
|
||||
@@ -71,6 +91,10 @@ struct VOXELFORGE_API FVoxelStrateRollInfo
|
||||
FString FailureReason;
|
||||
|
||||
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
|
||||
FVoxelStrateArchetypeParams ArchetypeParams;
|
||||
|
||||
// Compatibility view for the original tunnel-only API. For a non-tunnel roll, the native
|
||||
// result is in ArchetypeParams and this member is not the active candidate vector.
|
||||
FStrateGenerationParams Params;
|
||||
|
||||
TArray<int32> ParentEntryIndices;
|
||||
@@ -81,17 +105,22 @@ struct VOXELFORGE_API FVoxelStrateRollInfo
|
||||
/**
|
||||
* Known-good strate vectors plus their measured field spread.
|
||||
*
|
||||
* `LoadFromSettings` walks both the pool and fixed-strate references, resolves each definition,
|
||||
* de-duplicates it by asset path, and admits only TunnelNetwork/Underwater definitions because
|
||||
* those are the two archetypes whose authored vector is `GenerationParams`. Other archetypes have
|
||||
* different parameter structs; treating their default `GenerationParams` as authored data would
|
||||
* manufacture a false corpus.
|
||||
* `LoadFromAssetRegistry` enumerates every project UVoxelStrateDefinition through the Asset
|
||||
* Registry, explicitly excluding Saved/Autosaves and Saved/Cooked copies. It also adds one
|
||||
* known-good default vector for every archetype/family in VoxelStrateTypes.h. Parents are selected
|
||||
* only inside the exact archetype group; no cross-archetype blend is attempted.
|
||||
*/
|
||||
class VOXELFORGE_API FVoxelStrateCorpus
|
||||
{
|
||||
public:
|
||||
void Reset();
|
||||
|
||||
bool LoadFromAssetRegistry(FString& OutReport);
|
||||
|
||||
/**
|
||||
* Compatibility alias retained for callers compiled against the first Tier 4a pass. The
|
||||
* settings argument is audited only for diagnostics; it is not a corpus input.
|
||||
*/
|
||||
bool LoadFromSettings(const UVoxelSettings* Settings, FString& OutReport);
|
||||
bool LoadFromDefinitions(const TArray<UVoxelStrateDefinition*>& Definitions, FString& OutReport);
|
||||
|
||||
@@ -99,6 +128,9 @@ public:
|
||||
bool AddEntry(const FString& SourcePath, const FString& SourceName,
|
||||
ECaveGeneratorType Archetype, const FStrateGenerationParams& Params,
|
||||
float Weight = 1.0f);
|
||||
bool AddEntry(const FString& SourcePath, const FString& SourceName,
|
||||
ECaveGeneratorType Archetype, const FVoxelStrateArchetypeParams& Params,
|
||||
float Weight = 1.0f);
|
||||
|
||||
bool IsValid() const { return bSchemaValid && Entries.Num() > 0; }
|
||||
bool IsSchemaValid() const { return bSchemaValid; }
|
||||
@@ -109,6 +141,10 @@ public:
|
||||
const TArray<FVoxelStrateFieldSpread>& GetFieldSpreads() const { return FieldSpreads; }
|
||||
const TArray<FString>& GetSkippedDefinitions() const { return SkippedDefinitions; }
|
||||
|
||||
int32 NumForArchetype(ECaveGeneratorType Archetype) const;
|
||||
|
||||
const FVoxelStrateFieldSpread* FindSpread(
|
||||
ECaveGeneratorType Archetype, const FString& FieldName) const;
|
||||
const FVoxelStrateFieldSpread* FindSpread(const FString& FieldName) const;
|
||||
|
||||
/** Stable hash of source paths, archetypes, weights, and every listed scalar field. */
|
||||
@@ -149,3 +185,8 @@ VOXELFORGE_API FStrateGenerationParams VF_RollStrateParams(
|
||||
VOXELFORGE_API bool VF_AreStrateParamsBitIdentical(
|
||||
const FStrateGenerationParams& A, const FStrateGenerationParams& B);
|
||||
|
||||
/** Bitwise comparison of the native vector for one archetype family. */
|
||||
VOXELFORGE_API bool VF_AreStrateArchetypeParamsBitIdentical(
|
||||
const FVoxelStrateArchetypeParams& A,
|
||||
const FVoxelStrateArchetypeParams& B,
|
||||
ECaveGeneratorType Archetype);
|
||||
Reference in new issue
Block a user