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
|
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.
|
`UVoxelStrateDefinition::Biomes[]`. Generator-agnostic (surface biomes now, cave biomes later). §8.14.
|
||||||
|
|
||||||
**`Public/VoxelStrateComposer.h` + `Private/VoxelStrateComposer.cpp`** (NEW, Tier 4a) — offline
|
**`Public/VoxelStrateComposer.h` + `Private/VoxelStrateComposer.cpp`** (Tier 4a) — offline
|
||||||
`FVoxelStrateCorpus` and `VF_RollStrateParams`. Walks the settings asset's fixed/pool definition
|
`FVoxelStrateCorpus` and `VF_RollStrateParams`. `LoadFromAssetRegistry` enumerates every project
|
||||||
references, admits only `TunnelNetwork` / `Underwater` vectors (the archetypes that author
|
`UVoxelStrateDefinition` through the Asset Registry, excludes `Saved/Autosaves` and `Saved/Cooked`
|
||||||
`GenerationParams`), measures every X-macro field's min/max/mean/population stddev, and exposes the
|
copies, and adds one `VoxelStrateTypes.h` default vector for each of the eight exact archetypes.
|
||||||
explicit non-tunable list. Rolls are pure in `(corpus contents, seed, index)`: weighted 2–3-parent
|
The corpus is grouped by exact `ECaveGeneratorType`: the 2026-09-04 run loaded **4 project
|
||||||
same-archetype selection → `FStrateGenerationParams::Lerp` → ±15% of measured field range jitter →
|
vectors + 8 defaults = 12 members** in **8 groups**. Sibling families use their native structs
|
||||||
editor-reflection clamps → ordered-pair repair. Bool `bTunnelsFlowTowardOrigin` inherits from the
|
(`FSlabGenerationParams`, `FMazeGenerationParams`, `FSurfaceGenerationParams`,
|
||||||
dominant parent; integer/enum SNAP fields are not jittered. This API is not called by runtime
|
`FVerticalShaftParams`, `FFloatingIslandParams`); no cross-archetype blend is attempted.
|
||||||
generation. The first project corpus run found one eligible `DA_Strate3` vector, so all measured
|
Spreads are measured per group, including the plain-native tunnel transport fields for reporting.
|
||||||
spreads were zero; that limitation is recorded by the Tier 4a test/report rather than hidden.
|
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`
|
### 3.9 Player edits — `Public/VoxelDiffLayer.h` + `.cpp`
|
||||||
`UVoxelDiffLayer : UObject` (h:77). Stores `FVoxelModification` (h:43: Center/Radius/Strength;
|
`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. |
|
| `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. |
|
| `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`). |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `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
|
`Public/VoxelStrateComposer.h` + `Private/VoxelStrateComposer.cpp` now implement the corpus and
|
||||||
the deterministic roll. `Private/Tests/VoxelForgeComposerParameterRollTest.cpp` is the offline
|
the deterministic roll. `Private/Tests/VoxelForgeComposerParameterRollTest.cpp` is the offline
|
||||||
measurement harness. The roll uses `FStrateGenerationParams::Lerp`, chooses 2–3 weighted parents
|
measurement harness. The roll uses the native family blend (`FStrateGenerationParams::Lerp` for
|
||||||
inside one archetype group, applies jitter of **±15% of each field's measured corpus range
|
the tunnel family), chooses 2–3 weighted parents **inside one exact archetype group**, applies
|
||||||
(`max-min`)**, then applies reflected `ClampMin`/`ClampMax` metadata. It does not use clamps as
|
jitter of **±15% of each field's measured corpus range (`max-min`)**, then applies reflected
|
||||||
distribution ranges, does not touch generation, and leaves `WorldRadiusVoxels` at its default 0.
|
`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,
|
The original one-vector result was caused by the old loader following `DA_Settings`' fixed-strate
|
||||||
and accepts only `TunnelNetwork` / `Underwater`: those are the only archetypes whose authored vector
|
and pool references. An audit of those references finds **one unique path**,
|
||||||
is `GenerationParams`. The current `DA_Settings` asset contains **one unique resolved reference**,
|
`/Game/VoxelForge/DA_Strate3.DA_Strate3`, whose archetype is `TunnelNetwork`; that is why the
|
||||||
`DA_Strate3`, and it is `TunnelNetwork`; the other authored archetype assets are not in that
|
previous corpus only loaded one asset. The current loader instead asks the Asset Registry for every
|
||||||
settings corpus. Consequently every measured field has zero spread, all 64 rolls repeat the same
|
project `UVoxelStrateDefinition`, sorts and de-duplicates the results, accepts `/Game` packages,
|
||||||
known-good vector (parent weights still roll deterministically), and this run did not exercise
|
and ignores `Saved/Autosaves` and `Saved/Cooked` copies. The scan found **4 project assets**, with
|
||||||
non-zero jitter. That is a corpus-size finding, not a rate optimization.
|
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
|
The reflection/use audit found **36 excluded fields**: the 34 terrain-op transport fields
|
||||||
`TerraceStepHeight`, `TerraceHardness`, `TerraceNoiseDisplacement`, `LayerLineSpacing`,
|
`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`,
|
`ChimneyMaxRadius`, `ChimneyHeight`, `DomeDensity`, `DomeMinRadius`, `DomeMaxRadius`,
|
||||||
`DomeHeightRatio`, `PinchDensity`, `PinchStrength`, `PinchLength` — populated per room by
|
`DomeHeightRatio`, `PinchDensity`, `PinchStrength`, `PinchLength` — populated per room by
|
||||||
`UVoxelTerrainOpDefinition` — plus the manager-owned runtime bounds `StrateTopWorldZ` and
|
`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.
|
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.
|
The bool policy is explicit: `bTunnelsFlowTowardOrigin` is inherited from the dominant parent.
|
||||||
`OriginRoomMaxConnections` and `RoughnessNoiseType` retain `Lerp`'s existing SNAP behavior and
|
`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
|
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.
|
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
|
The fed 64-candidate run (step 4 sweep, radius 256, max 8,000,000 cells, fixture seed 1337)
|
||||||
64/64 non-vacuous candidates, 64/64 largest-component share ≥ 0.50, and 64/64 exact unsnapped
|
produced **59/64 survivors (92.2%)**. There were 60 non-vacuous candidates, 60 with largest
|
||||||
arrival→departure law passes. Every row was `air=0.520272`, `largest share=0.999996`,
|
component share ≥ 0.50, 63 exact unsnapped arrival→departure law passes, and no roll failures.
|
||||||
`walkable=0.057995`, `feature scale=20.000000`, `Connected`; total runtime was **542.220 s**.
|
The complete 64-row table is emitted by `VoxelForge.Composer.ParameterRoll`; each row reports
|
||||||
The §6.2 check saw **2,560 Mixed, 0 AllSolid, 0 AllAir, 0 brute-force voxels, 0 violations**:
|
archetype, weighted parents, air fraction, largest share, walkable fraction, feature scale, and
|
||||||
this is explicitly **vacuous**, not evidence that rolled bounds are sound, because the current cave
|
arrival→departure verdict. The final full-suite run took **112.574 s** total (**0.015 s** corpus
|
||||||
box path emitted no uniform verdict. The test leaves §6.2 open and reports that fact rather than
|
load, **112.556 s** roll/measurement).
|
||||||
turning it into a workaround.
|
|
||||||
|
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
|
### 3.4 The measurement pass — one grid, one flood fill, three jobs
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,15 @@
|
|||||||
|
|
||||||
#include "Misc/AutomationTest.h"
|
#include "Misc/AutomationTest.h"
|
||||||
#include "HAL/PlatformTime.h"
|
#include "HAL/PlatformTime.h"
|
||||||
|
#include "AssetRegistry/AssetData.h"
|
||||||
|
#include "AssetRegistry/AssetRegistryModule.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
#include "VoxelForgeTestFixture.h"
|
#include "VoxelForgeTestFixture.h"
|
||||||
|
#include "VoxelSettings.h"
|
||||||
#include "VoxelStrateComposer.h"
|
#include "VoxelStrateComposer.h"
|
||||||
#include "VoxelStrateMeasure.h"
|
#include "VoxelStrateMeasure.h"
|
||||||
|
#include "VoxelTerrainOpDefinition.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -53,6 +58,124 @@ namespace
|
|||||||
*VF_FormatSpreadValue(Spread.ClampMax));
|
*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,
|
FString VF_FormatParents(const FVoxelStrateCorpus& Corpus,
|
||||||
const FVoxelStrateRollInfo& Roll)
|
const FVoxelStrateRollInfo& Roll)
|
||||||
{
|
{
|
||||||
@@ -233,21 +356,81 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
FVoxelStrateCorpus Corpus;
|
FVoxelStrateCorpus Corpus;
|
||||||
FString CorpusReport;
|
FString CorpusReport;
|
||||||
const double CorpusStartSeconds = FPlatformTime::Seconds();
|
const double CorpusStartSeconds = FPlatformTime::Seconds();
|
||||||
const bool bCorpusLoaded = Corpus.LoadFromSettings(AuthoredSettings, CorpusReport);
|
const bool bCorpusLoaded = Corpus.LoadFromAssetRegistry(CorpusReport);
|
||||||
const double CorpusSeconds = FPlatformTime::Seconds() - CorpusStartSeconds;
|
const double CorpusSeconds = FPlatformTime::Seconds() - CorpusStartSeconds;
|
||||||
AddInfo(FString::Printf(TEXT("%s Load time %.3fs; contents hash 0x%08x; clamp metadata at "
|
AddInfo(FString::Printf(TEXT("%s Load time %.3fs; contents hash 0x%08x; clamp metadata at "
|
||||||
"build=%s, promised at cooked runtime=%s."),
|
"build=%s, promised at cooked runtime=%s."),
|
||||||
*CorpusReport, CorpusSeconds, Corpus.GetContentsHash(),
|
*CorpusReport, CorpusSeconds, Corpus.GetContentsHash(),
|
||||||
Corpus.AreClampMetadataAvailableAtBuild() ? TEXT("yes") : TEXT("no"),
|
Corpus.AreClampMetadataAvailableAtBuild() ? TEXT("yes") : TEXT("no"),
|
||||||
Corpus.AreClampMetadataAvailableAtRuntime() ? 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(
|
FString SpreadTable = TEXT(
|
||||||
"CORPUS SPREAD (population stddev; jitter range = ±15% of max-min; clamps are safety only)\n"
|
"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())
|
for (const FVoxelStrateFieldSpread& Spread : Corpus.GetFieldSpreads())
|
||||||
{
|
{
|
||||||
SpreadTable += FString::Printf(
|
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,
|
*Spread.FieldName,
|
||||||
VF_FieldKindName(Spread.Kind),
|
VF_FieldKindName(Spread.Kind),
|
||||||
Spread.bExcluded ? TEXT("yes") : TEXT("no"),
|
Spread.bExcluded ? TEXT("yes") : TEXT("no"),
|
||||||
@@ -270,9 +453,10 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
AddInfo(ExclusionTable);
|
AddInfo(ExclusionTable);
|
||||||
AddInfo(TEXT(
|
AddInfo(TEXT(
|
||||||
"Bool policy: bTunnelsFlowTowardOrigin is inherited from the dominant weighted parent; "
|
"Bool policy: bTunnelsFlowTowardOrigin is inherited from the dominant weighted parent; "
|
||||||
"int/enum SNAP fields use FStrateGenerationParams::Lerp and are not jittered. Parent "
|
"int/enum SNAP fields use the native family blend and are not jittered. Parent selection "
|
||||||
"selection is weight-1.0 (equal) because UVoxelStrateDefinition has no corpus-weight field; "
|
"is weight-1.0 (equal) because UVoxelStrateDefinition has no corpus-weight field; parents "
|
||||||
"parents are selected only within one archetype group."));
|
"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"),
|
TestTrue(TEXT("the reflected parameter schema is covered or explicitly excluded"),
|
||||||
Corpus.IsSchemaValid());
|
Corpus.IsSchemaValid());
|
||||||
@@ -281,7 +465,7 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
{
|
{
|
||||||
AddError(FString::Printf(
|
AddError(FString::Printf(
|
||||||
TEXT("Cannot run the 64-candidate measurement: %s"),
|
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()));
|
: *Corpus.GetSchemaError()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -326,6 +510,7 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
int64 TotalBoxCheckedVoxels = 0;
|
int64 TotalBoxCheckedVoxels = 0;
|
||||||
int32 TotalBoxViolations = 0;
|
int32 TotalBoxViolations = 0;
|
||||||
TArray<int32> BoxViolationCandidates;
|
TArray<int32> BoxViolationCandidates;
|
||||||
|
FString FirstBoxViolation;
|
||||||
|
|
||||||
const double RollAndMeasureStartSeconds = FPlatformTime::Seconds();
|
const double RollAndMeasureStartSeconds = FPlatformTime::Seconds();
|
||||||
for (int32 CandidateIndex = 0; CandidateIndex < NumCandidates; ++CandidateIndex)
|
for (int32 CandidateIndex = 0; CandidateIndex < NumCandidates; ++CandidateIndex)
|
||||||
@@ -349,23 +534,47 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
continue;
|
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
|
// 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.
|
// world only; the runtime manager/generation code and authored assets are untouched.
|
||||||
UVoxelStrateDefinition* CandidateDefinition =
|
UVoxelStrateDefinition* CandidateDefinition =
|
||||||
World.Definitions[CandidateStrateIndex].Get();
|
World.Definitions[CandidateStrateIndex].Get();
|
||||||
CandidateDefinition->GeneratorType = Roll.Archetype;
|
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->bUseOperatorStack = true;
|
||||||
CandidateDefinition->TransitionType = EVoxelStrateTransition::Hard;
|
CandidateDefinition->TransitionType = EVoxelStrateTransition::Hard;
|
||||||
World.Reinitialize();
|
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),
|
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(
|
const FVoxelStrateMetrics Metrics = VF_MeasureStrate(
|
||||||
*World.Generator, *World.StrateManager, CandidateStrateIndex, MeasureSettings);
|
*World.Generator, *World.StrateManager, CandidateStrateIndex, MeasureSettings);
|
||||||
@@ -438,6 +647,10 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
if (BoxReport.Violations > 0)
|
if (BoxReport.Violations > 0)
|
||||||
{
|
{
|
||||||
BoxViolationCandidates.Add(CandidateIndex);
|
BoxViolationCandidates.Add(CandidateIndex);
|
||||||
|
if (FirstBoxViolation.IsEmpty())
|
||||||
|
{
|
||||||
|
FirstBoxViolation = BoxReport.FirstViolation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CandidateTable += FString::Printf(
|
CandidateTable += FString::Printf(
|
||||||
@@ -481,8 +694,8 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
AddError(FString::Printf(
|
AddError(FString::Printf(
|
||||||
TEXT("ROLLED BOX-VERDICT VIOLATION: %d candidate(s) produced a false AllSolid/AllAir "
|
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 "
|
"claim; first candidate index %d. This closes the test red because §6.2 bounds "
|
||||||
"were not sound for the rolled parameter space."),
|
"were not sound for the rolled parameter space. First violation: %s"),
|
||||||
BoxViolationCandidates.Num(), BoxViolationCandidates[0]));
|
BoxViolationCandidates.Num(), BoxViolationCandidates[0], *FirstBoxViolation));
|
||||||
}
|
}
|
||||||
if (TotalBoxProved == 0)
|
if (TotalBoxProved == 0)
|
||||||
{
|
{
|
||||||
@@ -504,4 +717,3 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif // WITH_DEV_AUTOMATION_TESTS
|
#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;
|
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
|
struct VOXELFORGE_API FVoxelStrateFieldSpread
|
||||||
{
|
{
|
||||||
|
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
|
||||||
|
FString ParamStructName;
|
||||||
FString FieldName;
|
FString FieldName;
|
||||||
EVoxelStrateFieldKind Kind = EVoxelStrateFieldKind::Continuous;
|
EVoxelStrateFieldKind Kind = EVoxelStrateFieldKind::Continuous;
|
||||||
bool bExcluded = false;
|
bool bExcluded = false;
|
||||||
@@ -50,12 +52,30 @@ struct VOXELFORGE_API FVoxelStrateFieldSpread
|
|||||||
double ClampMax = 0.0;
|
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. */
|
/** One known-good authored vector and the archetype that gives it meaning. */
|
||||||
struct VOXELFORGE_API FVoxelStrateCorpusEntry
|
struct VOXELFORGE_API FVoxelStrateCorpusEntry
|
||||||
{
|
{
|
||||||
FString SourcePath;
|
FString SourcePath;
|
||||||
FString SourceName;
|
FString SourceName;
|
||||||
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
|
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;
|
FStrateGenerationParams Params;
|
||||||
|
|
||||||
// The current asset format has no corpus weight field. Loaded hand-authored entries therefore
|
// The current asset format has no corpus weight field. Loaded hand-authored entries therefore
|
||||||
@@ -71,6 +91,10 @@ struct VOXELFORGE_API FVoxelStrateRollInfo
|
|||||||
FString FailureReason;
|
FString FailureReason;
|
||||||
|
|
||||||
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
|
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;
|
FStrateGenerationParams Params;
|
||||||
|
|
||||||
TArray<int32> ParentEntryIndices;
|
TArray<int32> ParentEntryIndices;
|
||||||
@@ -81,17 +105,22 @@ struct VOXELFORGE_API FVoxelStrateRollInfo
|
|||||||
/**
|
/**
|
||||||
* Known-good strate vectors plus their measured field spread.
|
* Known-good strate vectors plus their measured field spread.
|
||||||
*
|
*
|
||||||
* `LoadFromSettings` walks both the pool and fixed-strate references, resolves each definition,
|
* `LoadFromAssetRegistry` enumerates every project UVoxelStrateDefinition through the Asset
|
||||||
* de-duplicates it by asset path, and admits only TunnelNetwork/Underwater definitions because
|
* Registry, explicitly excluding Saved/Autosaves and Saved/Cooked copies. It also adds one
|
||||||
* those are the two archetypes whose authored vector is `GenerationParams`. Other archetypes have
|
* known-good default vector for every archetype/family in VoxelStrateTypes.h. Parents are selected
|
||||||
* different parameter structs; treating their default `GenerationParams` as authored data would
|
* only inside the exact archetype group; no cross-archetype blend is attempted.
|
||||||
* manufacture a false corpus.
|
|
||||||
*/
|
*/
|
||||||
class VOXELFORGE_API FVoxelStrateCorpus
|
class VOXELFORGE_API FVoxelStrateCorpus
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void Reset();
|
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 LoadFromSettings(const UVoxelSettings* Settings, FString& OutReport);
|
||||||
bool LoadFromDefinitions(const TArray<UVoxelStrateDefinition*>& Definitions, FString& OutReport);
|
bool LoadFromDefinitions(const TArray<UVoxelStrateDefinition*>& Definitions, FString& OutReport);
|
||||||
|
|
||||||
@@ -99,6 +128,9 @@ public:
|
|||||||
bool AddEntry(const FString& SourcePath, const FString& SourceName,
|
bool AddEntry(const FString& SourcePath, const FString& SourceName,
|
||||||
ECaveGeneratorType Archetype, const FStrateGenerationParams& Params,
|
ECaveGeneratorType Archetype, const FStrateGenerationParams& Params,
|
||||||
float Weight = 1.0f);
|
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 IsValid() const { return bSchemaValid && Entries.Num() > 0; }
|
||||||
bool IsSchemaValid() const { return bSchemaValid; }
|
bool IsSchemaValid() const { return bSchemaValid; }
|
||||||
@@ -109,6 +141,10 @@ public:
|
|||||||
const TArray<FVoxelStrateFieldSpread>& GetFieldSpreads() const { return FieldSpreads; }
|
const TArray<FVoxelStrateFieldSpread>& GetFieldSpreads() const { return FieldSpreads; }
|
||||||
const TArray<FString>& GetSkippedDefinitions() const { return SkippedDefinitions; }
|
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;
|
const FVoxelStrateFieldSpread* FindSpread(const FString& FieldName) const;
|
||||||
|
|
||||||
/** Stable hash of source paths, archetypes, weights, and every listed scalar field. */
|
/** 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(
|
VOXELFORGE_API bool VF_AreStrateParamsBitIdentical(
|
||||||
const FStrateGenerationParams& A, const FStrateGenerationParams& B);
|
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