test(composer): the handmade corpus is load-bearing for QUALITY, not validity

Answers Jahni's standing wish directly: "in the best of both worlds, i'd
like the system to not rely at all on the handmade ones."

Three arms, same size, same measurement, same criteria:

  approach              survival   walkable mean  feature-scale mean
  corpus blend (today)  13/16 81%    0.0661          92.3
  naive uniform          7/16 44%    0.0126          64.6
  constraint-sampled     7/16 44%    0.0129          31.4

⭐ THE ANSWER IS NOT THE SURVIVAL RATE, IT IS THE QUALITY GAP. Constraint
sampling produces VALID worlds -- it passes the primordial law more often
than naive rolling (1 law failure vs 3) -- but the survivors are materially
worse: ~5x lower walkable fraction and ~3x lower feature scale. A composer
that emits technically-passing but uniformly mediocre places has not solved
the problem.

So the corpus carries something the declared constraints do not. §3.3 said
independent rolling destroys the CORRELATIONS; stating those correlations
explicitly recovers validity but not the tacit quality that authored
vectors encode. That is worth knowing and it was not knowable by argument.

⇒ VERDICT: keep the corpus in production. Constraint sampling is a
corpus-free BOOTSTRAP (it can seed a corpus from nothing), and PROMOTION
(Tier 5) is the real route off handmade content -- dilution, not removal.
Naive rolling is rejected outright.

⚠️ UNDER-POWERED, stated plainly: 16 candidates per arm, not the 256 the
spec asked for -- the fixed 8M-cell box/law workload made 256x3 too slow to
finish. The 5x walkable gap is large enough to be believable at n=16, but
the survival rates (44% vs 44%) are not separable at that sample size. Do
not quote these as precise.

Authored-corpus audit, and Codex read it correctly: 3 of 12 vectors violate
the proposed relations (DA_Strate3 room coverage 2.68 vs bound 0.35;
Default_SurfaceWorld ground bound; Default_VerticalShafts shaft radius 5 vs
roughness margin 5.875). That does NOT mean those assets are broken -- it
means the new relations are conservative HYPOTHESES, not universal corpus
invariants. Recorded as such.

Box law held across all three arms: 768 proved, 673,486 voxels brute-forced,
0 violations. §6.2 did not reopen under corpus-free parameters.

Also noted: "uniform within clamps" was underspecified in my spec -- many
fields have no finite upper clamp. Both corpus-free arms use the same
declared H-relative envelopes so the comparison stays honest.

Build clean, CorpusFree green, WorldRadiusVoxels=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-05 00:04:24 +02:00
1 parent d717bc74ff
commit 43eaf1ef0e
5 files changed
+2131

No files matched your search

+17
View File
@@ -477,6 +477,22 @@ it. `RequiredResources` / `ProvidedResources` close the room-state hole that cha
could not express. `VoxelForgeComposerStructureRollTest` rolls 64 candidates, measures them through could not express. `VoxelForgeComposerStructureRollTest` rolls 64 candidates, measures them through
the offline sampler, and brute-forces every uniform box verdict from the novel stack itself. the offline sampler, and brute-forces every uniform box verdict from the novel stack itself.
`EVoxelStrateCorpusFreeSamplingMode`, `VF_RollStrateParamsCorpusFree`, and
`VF_ValidateStrateCorpusFreeConstraints` are the offline corpus-free control/experiment. They never
read a corpus entry or strate asset: `NaiveUniform` rolls every live scalar independently inside a
finite, code-declared envelope, while `ConstraintSampled` draws geometry quantities and derives
dependent quantities from the generator equations before applying the envelope as a final safety
net. `VoxelForgeComposerCorpusFreeTest.cpp` compares both arms with today's corpus blend using the
same structure recipe, fixed measurement window, one arrival/departure law, and box-verdict brute
force. The requested 256×3 and 64×3 runs were abandoned before aggregate output because the fixed
8M-cell law/box workload was too slow; the completed equal-arm run is 16 candidates per arm and
22.995 s inside the test. Its final result is **13/16 corpus-blend survivors, 7/16 naive, and
7/16 constraint-sampled**. Survivor quality is not equivalent: corpus survivors averaged
**0.066071 walkable / 92.307693 voxel feature scale**, versus **0.012566 / 64.571426** for naive
and **0.012851 / 31.428572** for constrained. All three arms had **0 box-verdict violations**;
proved boxes / checked lattice voxels were **196 / 260,876**, **310 / 412,610**, and
**262 / 348,722** respectively.
Public/VoxelWorld.h + Private/VoxelWorld.cpp — ApplyComposerCandidate exposes the four Public/VoxelWorld.h + Private/VoxelWorld.cpp — ApplyComposerCandidate exposes the four
Live Edit|Composer properties and the CallInEditor button. It preflights the candidate, pauses Live Edit|Composer properties and the CallInEditor button. It preflights the candidate, pauses
generation before installing the manager override, and calls the existing epoch-aware full reset. generation before installing the manager override, and calls the existing epoch-aware full reset.
@@ -560,6 +576,7 @@ The plugin's first tests (`OPSTACK-PLAN.md` Phase 0.5). Run them from the editor
| `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. Bootstrap follow-up: **1,422 applications / 168 distinct fields**; **38/64 survival**, feature scale **0→376**, walkable **0.000000→0.286766** (no broadening beyond the prior run); **1,356 Mixed + 608 AllSolid + 596 AllAir = 1,204 proved boxes**, **1,602,524 lattice voxels checked, 0 violations**, 146.250 s total in the final full-namespace run. | | `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. Bootstrap follow-up: **1,422 applications / 168 distinct fields**; **38/64 survival**, feature scale **0→376**, walkable **0.000000→0.286766** (no broadening beyond the prior run); **1,356 Mixed + 608 AllSolid + 596 AllAir = 1,204 proved boxes**, **1,602,524 lattice voxels checked, 0 violations**, 146.250 s total in the final full-namespace run. |
| ″ | `VoxelForge.Composer.TerrainDetailLiveness` | Fixed 4,096-point `GetDensityAt` lattice, legacy and operator-stack paths; changes one terrain-detail group at a time with an empty terrain-op pool. Proves 9 live groups / 23 fields and 3 dead groups / 11 fields; all 24 rows match. | | ″ | `VoxelForge.Composer.TerrainDetailLiveness` | Fixed 4,096-point `GetDensityAt` lattice, legacy and operator-stack paths; changes one terrain-detail group at a time with an empty terrain-op pool. Proves 9 live groups / 23 fields and 3 dead groups / 11 fields; all 24 rows match. |
| `VoxelForgeComposerStructureRollTest.cpp` | `VoxelForge.Composer.StructureRoll` | Rolls root polarity → legal shape source → polarity-derived conversion → 4–8 declaration-legal modifiers → mandatory structural posts; blends the six native parameter families independently, measures 64 novel stacks, captures the same grid for the deterministic filled/contour XZ/XY preview, runs a separate step-1 radius-64 ROI pass for the 42 survivors, checks exact arrival→departure connectivity, rerolls every recipe/stack for determinism, and brute-forces every uniform box verdict. Focused run: **42/64 survival (65.6%)**, **64 distinct recipes**, **6,848 bootstrap applications / 107 distinct fields**, **659 proved boxes / 877,129 voxels / 0 violations**, **64 coarse filled + 64 coarse contour pairs**, **42 fine ROI filled + 42 fine contour pairs**, **184.659 s**, **0 refusals**. | | `VoxelForgeComposerStructureRollTest.cpp` | `VoxelForge.Composer.StructureRoll` | Rolls root polarity → legal shape source → polarity-derived conversion → 4–8 declaration-legal modifiers → mandatory structural posts; blends the six native parameter families independently, measures 64 novel stacks, captures the same grid for the deterministic filled/contour XZ/XY preview, runs a separate step-1 radius-64 ROI pass for the 42 survivors, checks exact arrival→departure connectivity, rerolls every recipe/stack for determinism, and brute-forces every uniform box verdict. Focused run: **42/64 survival (65.6%)**, **64 distinct recipes**, **6,848 bootstrap applications / 107 distinct fields**, **659 proved boxes / 877,129 voxels / 0 violations**, **64 coarse filled + 64 coarse contour pairs**, **42 fine ROI filled + 42 fine contour pairs**, **184.659 s**, **0 refusals**. |
| `VoxelForgeComposerCorpusFreeTest.cpp` | `VoxelForge.Composer.CorpusFree` | Shares each structure recipe across today's corpus blend, naive independent uniform rolls, and constraint-sampled rolls. The completed equal-arm run uses **16 candidates per arm** (256×3 and 64×3 were stopped before aggregate output for runtime), step 4 / radius 256 / `MaxCells=8,000,000`, fixed passage-law mouths, and 40 box probes per candidate. Result: **13/16, 7/16, 7/16** survival; survivor walkable means **0.066071, 0.012566, 0.012851** and feature-scale means **92.307693, 64.571426, 31.428572**. Box checks: **196/260,876**, **310/412,610**, **262/348,722** proved/voxels, **0 violations** in every arm. |
| `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. |
+125
View File
@@ -470,6 +470,131 @@ and **0 violations** (1,901 Mixed, 266 AllSolid, 393 AllAir). The parameter-roll
proved **1,204 boxes**, checked **1,602,524 voxels**, and found **0 violations** (1,356 Mixed, 608 proved **1,204 boxes**, checked **1,602,524 voxels**, and found **0 violations** (1,356 Mixed, 608
AllSolid, 596 AllAir). A violation would reopen §6.2; this run did not. AllSolid, 596 AllAir). A violation would reopen §6.2; this run did not.
### Corpus-free measurement (2026-09-04)
The proposed alternative to copying correlations from examples was measured directly. The structure
recipe is shared by all three arms, so only parameter provenance changes:
* **Corpus blend (today):** the existing exact-archetype weighted parent blend, ±15% spread jitter,
and final metadata clamps.
* **Naive uniform:** every live scalar is independently uniform in the same finite corpus-free
envelope. No authored vector, default vector, blend, or corpus spread is read.
* **Constraint-sampled:** independent geometry quantities are drawn; dependent quantities are
calculated from the relations below, then the same envelope is applied as a final safety net.
There is an important wording issue in Part A: most fields have only `ClampMin` and no finite
`ClampMax` (`VoxelStrateTypes.h` has 135 minimums and 52 maximums across 216 UPROPERTYs), while
the native tunnel fields have no reflected metadata. “Uniform within clamps” is therefore undefined
for much of the parameter space. For this measurement, `VF_GetCorpusFreeRange` declares finite
H-relative operational envelopes for those fields; it does not use a corpus value or a default value
as a range. The exact envelope is in `VoxelStrateComposer.cpp:1087-1278`, and both corpus-free arms
use it so the comparison is fair. This makes the naive result conditional on an explicit finite
support, which is a spec caveat rather than a hidden tuning target.
#### Declared relations
These are sufficient conservative relations for the experiment, derived from the existing generator
equations. They are not claimed to be invariants of every existing authored asset; the audit below
checks that question rather than assuming it.
| relation | declaration used by `ConstraintSampled` | code that supplies the geometry/math |
|---|---|---|
| Room placement, overlap, and isolation | Let `S=RoomSpacing`, `D=RoomDensity`, and `R=MaxRoomRadius`. Draw target area coverage `C = πR²D/S²` in `0.08..0.18`; derive `R = S*sqrt(C/(πD))`; require `C∈[0.02,0.35]` and `R≤S/2`. The coverage interval is an explicit experiment support for the room occupancy equation, not a generator hard limit. | `VoxelCaveMorphology.cpp:1305-1326` rolls room existence, jitter, and radius; `VoxelCaveMorphology.cpp:1319-1320` gives the `[0.15,0.85]` cell jitter. Implemented/validated at `VoxelStrateComposer.cpp:1522-1540` and `:3094-3104`. |
| Tunnel reach | Neighboring-room horizontal jitter can be `1.7S` in each axis, hence `1.7S√2`; vertical center separation can be `2*R*RoomHeightRatio`. Derive `MaxTunnelLength` as at least the Euclidean sum of those bounds. This matches the generator’s distance gate. | `VoxelCaveMorphology.cpp:1388` and `:1502` reject out-of-reach links; implemented/validated at `VoxelStrateComposer.cpp:1557-1564` and `:3114-3121`. |
| Room vertical fit | `BoundarySealThickness + max(MaxRoomRadius,OriginRoomRadius)*RoomHeightRatio < H/2`, because room centers are restricted to the seal-free interval and the room Z radius is `Radius*RoomHeightRatio`. | `VoxelCaveMorphology.cpp:137-140` and `:248-250` establish the room/seal buffer; validated at `VoxelStrateComposer.cpp:3106-3112`. |
| Slab floor/ceiling | Worst floor is `H*FloorRelativeHeight + 1.25*FloorRoughness`; worst ceiling is `H*CeilingRelativeHeight - 1.25*CeilingRoughness`. Derive both relative heights from seal + clearance and require a two-voxel void. | `VoxelCaveMorphology.cpp:329-362` and `VoxelGenerator.cpp:2175-2212`; implemented/validated at `VoxelStrateComposer.cpp:1602-1626` and `:3143-3185`. |
| Maze corridor | Derive `CorridorRadius = CellSize*(0.12..0.24)` so it leaves lattice wall; cap roughness below `CorridorRadius+2` so the centerline remains carveable. | `VoxelCaveMorphology.cpp:428-458` bounds the lattice; `VoxelGenerator.cpp:2412-2432` applies roughness and subtracts `2*BaseDensity`; implemented/validated at `VoxelStrateComposer.cpp:1629-1642` and `:3188-3216`. |
| Surface ground/cap and height features | The structural height is bounded by `ElevationRange*(1+MountainStrength)+2*1.25*SurfaceRoughness`. Add conservative budgets for cliff displacement (`CliffStrength*CliffSharpness` times that spread), terrace step, layer depth, and beach width. Derive `BaseGroundRelative` above the bottom seal and `CeilingRelative` below the top seal with cap roughness, undulation, ridge, overhang height, and two-voxel headroom. Cliff is sampled as a scalar and reduced only when this derived height budget cannot contain it. | `VoxelGenerator.cpp:2470-2517` is the structural height equation; `:2537-2557` is the cliff remap; `:2607-2654` is the cap; `:2670-2686` caps overhang height. Implemented/validated at `VoxelStrateComposer.cpp:1645-1724` and `:3219-3269`. |
| Vertical shafts and ledges | Require `ShaftMaxRadius≤ShaftSpacing/2`; require `ShaftMinRadius > 1.5*1.25*SurfaceRoughness + 0.25`; derive `ConnectorRadius > 1.5*1.25*SurfaceRoughness + 1`; derive ledge spacing greater than `2*(LedgeDepth + floor-clearance)`. | `VoxelCaveMorphology.cpp:628-654` moves landings out of roughness/ledge bands; `VoxelGenerator.cpp:3944-3954` derives tree roughness reach and `:4209-4214` applies ledges; implemented/validated at `VoxelStrateComposer.cpp:1730-1755` and `:3272-3310`. |
| Floating islands | Require `IslandMaxRadius≤IslandSpacing/2`; derive island radius and thickness so `BoundarySealThickness + max(0.2R,ThicknessRatio*R) < H/2`; require the minimum radius to exceed roughness plus SDF blend. | `VoxelCaveMorphology.cpp:852-935` computes island radius, underside, warp, and blend pad; `VoxelGenerator.cpp:4268-4344` starts with void and fills the island; implemented/validated at `VoxelStrateComposer.cpp:1757-1780` and `:3313-3355`. |
| Density sign/carve strength | Every solid-starting family samples `BaseDensity>0`; tunnel worm strength is derived above base density, and the maze/shaft/island paths use the existing ±`2*BaseDensity` carve/fill convention. | `VoxelGenerator.cpp:1059`, `:2085`, `:2359-2432`, `:3944-3954`, `:4200`, and `:4272`; the relation checks are in `VoxelStrateComposer.cpp:3029-3355`. |
Bools and enums are categorical, not arithmetic constraints. `bTunnelsFlowTowardOrigin` is a fair
50/50 draw; `OriginRoomMaxConnections` is an integer draw in the declared `0..12` support; and
`RoughnessNoiseType` is a uniform draw over all four enum values. Reflected boolean fields in the
sibling families are also fair 50/50. There are currently no non-tunnel reflected enums. The
category stream is separate from the scalar stream so neither arm’s scalar coupling changes the
categorical policy. The sampler is deterministic in `(archetype, seed, index, mode, H)`, uses no
retained RNG state, and clamps only after dependent equations (`VoxelStrateComposer.cpp:1782-1841`).
#### The actual three-way result
The requested **256×3** run was started and stopped after roughly six minutes without reaching an
aggregate report. A 64×3 attempt (including a repeat after law traversal was made conditional on
passing the fragmentation gate) also exceeded roughly 27 minutes without an aggregate report. Those
partial runs are not data. The completed equal-arm run therefore used **16 candidates per arm**;
all three arms ran the same 16 structure recipes, step-4 measurement over radius 256, `MaxCells=
8,000,000`, and one fixed arrival/departure pair. Criterion precedence is **vacuous → fragmented
(`LargestComponentShare < 0.50`) → law failed**. Law is intentionally skipped for fragmented
candidates because the earlier criterion already decides the candidate; `law skipped` is reported
separately. The completed test took **22.995 s** internally (**22.980 s** in roll/measurement).
| approach | survival | vacuous | fragmented | law failed | air range | walkable range | feature scale range |
|---|---:|---:|---:|---:|---:|---:|---:|
| corpus blend (today) | 13/16 (81.2%) | 0 | 1 | 2 | 0.003291..0.999878 | 0.000000..0.318692 | 4..332 |
| naive uniform | 7/16 (43.8%) | 3 | 3 | 3 | 0.003471..1.000000 | 0.000000..0.163436 | 0..152 |
| constraint-sampled | 7/16 (43.8%) | 3 | 5 | 1 | 0.002636..1.000000 | 0.000000..0.220178 | 0..52 |
The intermediate gates were corpus `16 non-vacuous / 15 largest-enough / 13 law passes`, naive
`13 / 10 / 7`, and constrained `13 / 8 / 7`. Observed law failures were 2, 3, and 1
respectively; the other candidates that did not reach the law were the reported fragmented cases.
No roll, build, measurement, stack-count, channel-order, or context failure occurred.
The constrained survivors are valid but not as good as the corpus survivors in this run:
| approach | survivor count | air range (mean) | walkable range (mean) | feature scale range (mean) |
|---|---:|---:|---:|---:|
| corpus blend (today) | 13 | 0.022004..0.999878 (0.801809) | 0.000000..0.318692 (0.066071) | 4..332 (92.307693) |
| naive uniform | 7 | 0.542744..0.996999 (0.856300) | 0.000068..0.044617 (0.012566) | 12..152 (64.571426) |
| constraint-sampled | 7 | 0.801857..0.976726 (0.886178) | 0.000237..0.047382 (0.012851) | 16..52 (31.428572) |
The constraint arm tied naive survival at 7/16, traded three naive law failures for five
fragmentation failures, and produced a higher mean air fraction but dramatically lower walkable and
feature-scale ranges than the corpus survivors. With only seven survivors in each corpus-free arm,
this is evidence of a quality gap, not a claim about a stable population distribution; it is already
enough to reject “technically valid means equivalent.”
#### Box-law and authored-corpus audit
Every arm performed 40 box probes per candidate. Non-`Mixed` verdicts were brute-forced over the
same unit lattice: **0 violations** in all arms.
| approach | Mixed | AllSolid | AllAir | proved | voxels checked | violations |
|---|---:|---:|---:|---:|---:|---:|
| corpus blend (today) | 444 | 69 | 127 | 196 | 260,876 | 0 |
| naive uniform | 330 | 168 | 142 | 310 | 412,610 | 0 |
| constraint-sampled | 378 | 90 | 172 | 262 | 348,722 | 0 |
| **total** | **1,152** | **327** | **441** | **768** | **673,486** | **0** |
The constraint sampler produced **0 declared-parameter violations** and stopped-on-violation count
0. `WorldRadiusVoxels` remained **0**. The shared structure recipe had 0 determinism failures;
each arm had 0 parameter-roll determinism failures, 0 invalid stack counts, 0 channel-order
failures, and 0 context violations.
The same declared relations were audited against all 12 current corpus vectors. **3/12 violate**
the conservative experiment relations:
* `DA_Strate3` (`TunnelNetwork`, H=128): room area coverage `2.68447`, above the declared `0.35`
upper support.
* `Default_SurfaceWorld` (`SurfaceWorld`, H=128): conservative ground lower bound `-15.35`,
below the bottom seal `4`.
* `Default_VerticalShafts` (`VerticalShafts`, H=128): minimum shaft radius `5`, below the roughness
margin `5.875`.
These are findings about the proposed sufficient relations, not proof that those authored worlds
are visibly broken: two are deliberately worst-case bounds and the first is an occupancy heuristic.
The corpus is living outside the new constraint envelope in three places, so the envelope cannot be
silently promoted to a universal validator without either weakening it or changing those assets.
#### Verdict
The corpus is still load-bearing for quality. Keep the corpus-blend arm in production and use the
constraint sampler as a corpus-free exploratory/bootstrap arm whose passing candidates can enter
Tier 5 promotion. Do not replace the production arm with naive uniform rolling. The data does not
justify dropping the handmade vectors today; it supports keeping them while promotion gradually
dilutes their share. A future larger sample is warranted after optimizing the fixed law/box pass,
but it should be treated as confirmation, not as permission to tune these observed counts.
### 3.4 The measurement pass — one grid, one flood fill, three jobs ### 3.4 The measurement pass — one grid, one flood fill, three jobs
Sample a candidate strate into a coarse voxel grid, flood-fill the air **once**, derive everything from Sample a candidate strate into a coarse voxel grid, flood-fill the air **once**, derive everything from
@@ -0,0 +1,795 @@
// Corpus-free composer measurement: current corpus blend versus independent and constrained rolls.
#if WITH_DEV_AUTOMATION_TESTS
#include "Misc/AutomationTest.h"
#include "HAL/PlatformTime.h"
#include "Math/RandomStream.h"
#include "VoxelForgeTestFixture.h"
#include "VoxelDensityOpStack.h"
#include "VoxelSettings.h"
#include "VoxelStrateComposer.h"
#include "VoxelStrateDefinition.h"
#include "VoxelStrateMeasure.h"
namespace
{
// 256x3 and 64x3 were attempted first. The latter exceeded 27 minutes before reaching
// its aggregate report because the fixed 8M-cell law/box checks are expensive for the
// corpus-free fields. Keep the final reduced count equal across all three arms.
constexpr int32 NumCandidates = 16;
constexpr float LargestComponentSurvivalThreshold = 0.50f;
constexpr int32 BoxChecksPerCandidate = 40;
constexpr int32 BoxStep = 1;
constexpr int32 BoxCells = 8;
const TCHAR* VF_ConnectivityName(EVoxelConnectivityResult Result)
{
switch (Result)
{
case EVoxelConnectivityResult::Connected: return TEXT("Connected");
case EVoxelConnectivityResult::NotConnectedAtThisResolution: return TEXT("NotConnected");
case EVoxelConnectivityResult::StartCellSolid: return TEXT("StartCellSolid");
case EVoxelConnectivityResult::GoalCellSolid: return TEXT("GoalCellSolid");
case EVoxelConnectivityResult::OutOfWindow: return TEXT("OutOfWindow");
case EVoxelConnectivityResult::CoarseLiedBudgetExhausted: return TEXT("CoarseLiedBudget");
}
return TEXT("Unknown");
}
int32 VF_FloorDiv(int32 A, int32 B)
{
const int32 Q = A / B;
const int32 R = A % B;
return (R != 0 && (R < 0) != (B < 0)) ? Q - 1 : Q;
}
struct FBlockSpec
{
EVoxelStrateParamBlock Block;
ECaveGeneratorType Archetype;
uint32 SeedSalt;
};
static const FBlockSpec GBlockSpecs[] =
{
{ EVoxelStrateParamBlock::TunnelNetwork, ECaveGeneratorType::TunnelNetwork, 0x1001u },
{ EVoxelStrateParamBlock::Slab, ECaveGeneratorType::FlatPlain, 0x1003u },
{ EVoxelStrateParamBlock::Maze, ECaveGeneratorType::Maze, 0x1005u },
{ EVoxelStrateParamBlock::Surface, ECaveGeneratorType::SurfaceWorld, 0x1007u },
{ EVoxelStrateParamBlock::VerticalShaft, ECaveGeneratorType::VerticalShafts, 0x1009u },
{ EVoxelStrateParamBlock::FloatingIsland, ECaveGeneratorType::FloatingIslands, 0x100Bu },
};
void VF_CopyRollBlock(FVoxelStrateArchetypeParams& OutParams,
const FVoxelStrateRollInfo& Roll,
ECaveGeneratorType Archetype)
{
switch (Archetype)
{
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::Underwater:
OutParams.TunnelNetworkParams = Roll.ArchetypeParams.TunnelNetworkParams;
break;
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
OutParams.SlabParams = Roll.ArchetypeParams.SlabParams;
break;
case ECaveGeneratorType::Maze:
OutParams.MazeParams = Roll.ArchetypeParams.MazeParams;
break;
case ECaveGeneratorType::SurfaceWorld:
OutParams.SurfaceParams = Roll.ArchetypeParams.SurfaceParams;
break;
case ECaveGeneratorType::VerticalShafts:
OutParams.VerticalShaftParams = Roll.ArchetypeParams.VerticalShaftParams;
break;
case ECaveGeneratorType::FloatingIslands:
OutParams.FloatingIslandParams = Roll.ArchetypeParams.FloatingIslandParams;
break;
default:
break;
}
}
void VF_SetAllRuntimeBounds(FVoxelStrateArchetypeParams& Params,
float TopWorldZ, float BottomWorldZ)
{
Params.TunnelNetworkParams.StrateTopWorldZ = TopWorldZ;
Params.TunnelNetworkParams.StrateBottomWorldZ = BottomWorldZ;
Params.SlabParams.StrateTopWorldZ = TopWorldZ;
Params.SlabParams.StrateBottomWorldZ = BottomWorldZ;
Params.MazeParams.StrateTopWorldZ = TopWorldZ;
Params.MazeParams.StrateBottomWorldZ = BottomWorldZ;
Params.SurfaceParams.StrateTopWorldZ = TopWorldZ;
Params.SurfaceParams.StrateBottomWorldZ = BottomWorldZ;
Params.VerticalShaftParams.StrateTopWorldZ = TopWorldZ;
Params.VerticalShaftParams.StrateBottomWorldZ = BottomWorldZ;
Params.FloatingIslandParams.StrateTopWorldZ = TopWorldZ;
Params.FloatingIslandParams.StrateBottomWorldZ = BottomWorldZ;
}
class FStackDensitySampler final : public IVoxelStrateDensitySampler
{
public:
explicit FStackDensitySampler(const FVoxelOpStack& InStack) : Stack(InStack) {}
float SampleDensity(float WorldX, float WorldY, float WorldZ) const override
{
return Stack.EvalMC(WorldX, WorldY, WorldZ);
}
private:
const FVoxelOpStack& Stack;
};
struct FBoxVerdictReport
{
int32 Mixed = 0;
int32 AllSolid = 0;
int32 AllAir = 0;
int32 Proved = 0;
int64 CheckedVoxels = 0;
int32 Violations = 0;
FString FirstViolation;
};
FBoxVerdictReport VF_CheckStackBoxVerdicts(const FVoxelOpStack& Stack,
const FVoxelOpContext& Context,
int32 CandidateIndex,
int32 BottomVoxelZ,
int32 TopVoxelZ)
{
FBoxVerdictReport Report;
constexpr int32 Extent = BoxStep * BoxCells;
constexpr int32 SpanCells = 40;
const int32 LoTile = VF_FloorDiv(BottomVoxelZ, Extent);
const int32 HiTile = FMath::Max(LoTile, VF_FloorDiv(TopVoxelZ, Extent));
FRandomStream Rng(0x6C617731 ^ CandidateIndex);
for (int32 BoxIndex = 0; BoxIndex < BoxChecksPerCandidate; ++BoxIndex)
{
const FIntVector Origin(
Rng.RandRange(-SpanCells, SpanCells) * Extent,
Rng.RandRange(-SpanCells, SpanCells) * Extent,
Rng.RandRange(LoTile, HiTile) * Extent);
const FBox Box(
FVector((float)(Origin.X - BoxStep), (float)(Origin.Y - BoxStep),
(float)(Origin.Z - BoxStep)),
FVector((float)(Origin.X + (BoxCells + 1) * BoxStep),
(float)(Origin.Y + (BoxCells + 1) * BoxStep),
(float)(Origin.Z + (BoxCells + 1) * BoxStep)));
const EVoxelTileClass Verdict = Stack.ClassifyBox(Box, Context);
if (Verdict == EVoxelTileClass::Mixed)
{
++Report.Mixed;
continue;
}
if (Verdict == EVoxelTileClass::AllSolid)
{
++Report.AllSolid;
}
else
{
++Report.AllAir;
}
++Report.Proved;
const int32 GridDim = BoxCells + 1;
const bool bClaimsSolid = Verdict == EVoxelTileClass::AllSolid;
for (int32 GZ = -1; GZ <= GridDim; ++GZ)
for (int32 GY = -1; GY <= GridDim; ++GY)
for (int32 GX = -1; GX <= GridDim; ++GX)
{
const float X = (float)(Origin.X + GX * BoxStep);
const float Y = (float)(Origin.Y + GY * BoxStep);
const float Z = (float)(Origin.Z + GZ * BoxStep);
const float Density = Stack.EvalMC(X, Y, Z);
++Report.CheckedVoxels;
const bool bAgrees = bClaimsSolid ? (Density < 0.0f) : (Density >= 0.0f);
if (!bAgrees)
{
++Report.Violations;
if (Report.FirstViolation.IsEmpty())
{
Report.FirstViolation = FString::Printf(
TEXT("candidate %d box %d origin (%d,%d,%d) said %s but MC density "
"at (%.0f,%.0f,%.0f) was %.9g"),
CandidateIndex, BoxIndex, Origin.X, Origin.Y, Origin.Z,
bClaimsSolid ? TEXT("AllSolid") : TEXT("AllAir"),
X, Y, Z, Density);
}
}
}
}
return Report;
}
struct FQualityStats
{
int32 Count = 0;
float AirMin = FLT_MAX;
float AirMax = -FLT_MAX;
float WalkableMin = FLT_MAX;
float WalkableMax = -FLT_MAX;
float FeatureMin = FLT_MAX;
float FeatureMax = -FLT_MAX;
double AirSum = 0.0;
double WalkableSum = 0.0;
double FeatureSum = 0.0;
void Add(const FVoxelStrateMetrics& Metrics)
{
++Count;
AirMin = FMath::Min(AirMin, Metrics.AirFraction);
AirMax = FMath::Max(AirMax, Metrics.AirFraction);
WalkableMin = FMath::Min(WalkableMin, Metrics.WalkableFraction);
WalkableMax = FMath::Max(WalkableMax, Metrics.WalkableFraction);
FeatureMin = FMath::Min(FeatureMin, Metrics.MedianFeatureScale);
FeatureMax = FMath::Max(FeatureMax, Metrics.MedianFeatureScale);
AirSum += Metrics.AirFraction;
WalkableSum += Metrics.WalkableFraction;
FeatureSum += Metrics.MedianFeatureScale;
}
FString RangeText(float Min, float Max) const
{
return Count > 0
? FString::Printf(TEXT("%.6f..%.6f"), Min, Max)
: TEXT("n/a");
}
FString SummaryText() const
{
return Count > 0
? FString::Printf(
TEXT("n=%d air=%s (mean %.6f), walkable=%s (mean %.6f), feature=%s (mean %.6f)"),
Count, *RangeText(AirMin, AirMax), (float)(AirSum / Count),
*RangeText(WalkableMin, WalkableMax), (float)(WalkableSum / Count),
*RangeText(FeatureMin, FeatureMax), (float)(FeatureSum / Count))
: TEXT("n=0");
}
};
struct FArmStats
{
const TCHAR* Name = TEXT("unknown");
int32 Requested = 0;
int32 RollFailures = 0;
int32 BuildFailures = 0;
int32 Unmeasured = 0;
int32 Vacuous = 0;
int32 Fragmented = 0;
int32 LawFailed = 0;
int32 LawFailedObserved = 0;
int32 LawSkipped = 0;
int32 Survivors = 0;
int32 NonVacuous = 0;
int32 LargestEnough = 0;
int32 LawPasses = 0;
int32 DeterminismFailures = 0;
int32 InvalidStackCount = 0;
int32 ChannelOrderFailures = 0;
int32 ContextViolations = 0;
int32 LawResults[6] = { 0, 0, 0, 0, 0, 0 };
FQualityStats AllQuality;
FQualityStats SurvivorQuality;
FBoxVerdictReport Box;
FString FirstFailure;
void RememberFailure(const FString& Failure)
{
if (FirstFailure.IsEmpty())
{
FirstFailure = Failure;
}
}
FString SurvivalText() const
{
return FString::Printf(TEXT("%d/%d (%.1f%%)"), Survivors, Requested,
Requested > 0 ? 100.0f * (float)Survivors / (float)Requested : 0.0f);
}
};
int32 VF_LawResultIndex(EVoxelConnectivityResult Result)
{
switch (Result)
{
case EVoxelConnectivityResult::Connected: return 0;
case EVoxelConnectivityResult::NotConnectedAtThisResolution: return 1;
case EVoxelConnectivityResult::StartCellSolid: return 2;
case EVoxelConnectivityResult::GoalCellSolid: return 3;
case EVoxelConnectivityResult::OutOfWindow: return 4;
case EVoxelConnectivityResult::CoarseLiedBudgetExhausted: return 5;
}
return 5;
}
bool VF_BitEqual(float A, float B)
{
uint32 ABits = 0;
uint32 BBits = 0;
FMemory::Memcpy(&ABits, &A, sizeof(ABits));
FMemory::Memcpy(&BBits, &B, sizeof(BBits));
return ABits == BBits;
}
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FVoxelForgeComposerCorpusFreeTest,
"VoxelForge.Composer.CorpusFree",
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
bool FVoxelForgeComposerCorpusFreeTest::RunTest(const FString& Parameters)
{
using namespace VoxelForgeTest;
(void)Parameters;
const double TestStartSeconds = FPlatformTime::Seconds();
const FString SettingsPath = TEXT("/Game/VoxelForge/DA_Settings.DA_Settings");
UVoxelSettings* AuthoredSettings = LoadObject<UVoxelSettings>(nullptr, *SettingsPath);
if (AuthoredSettings == nullptr)
{
AddError(FString::Printf(TEXT("Could not load %s; corpus-free measurement did not run."),
*SettingsPath));
return false;
}
FVoxelStrateCorpus Corpus;
FString CorpusReport;
const bool bCorpusLoaded = Corpus.LoadFromAssetRegistry(CorpusReport);
AddInfo(CorpusReport);
TestTrue(TEXT("corpus blend arm has a usable authored corpus"),
bCorpusLoaded && Corpus.IsValid());
if (!bCorpusLoaded || !Corpus.IsValid())
{
return false;
}
TestEqual(TEXT("corpus contains the four project vectors plus eight defaults"),
Corpus.GetEntries().Num(), 12);
FTestWorld World;
World.Build(1337, 2, true);
if (!World.IsValid())
{
AddError(World.WhyInvalid());
return false;
}
TestTrue(TEXT("corpus-free fixture keeps WorldRadiusVoxels at 0"),
World.Settings->WorldRadiusVoxels == 0.0f);
constexpr int32 CandidateStrateIndex = FTestWorld::SlotFlatPlain;
int32 TopVoxelZ = 0;
int32 BottomVoxelZ = 0;
if (!World.GetSlotVoxelZRange(CandidateStrateIndex, TopVoxelZ, BottomVoxelZ))
{
AddError(TEXT("corpus-free fixture has no candidate Z range"));
return false;
}
const float TopWorldZ = (float)TopVoxelZ + 1.0f;
const float BottomWorldZ = (float)BottomVoxelZ;
const float StrateHeightInVoxels = TopWorldZ - BottomWorldZ;
FVector ArrivalPoint = FVector::ZeroVector;
FVector DeparturePoint = FVector::ZeroVector;
int32 ArrivalCount = 0;
int32 DepartureCount = 0;
for (const FVoxelPassage& Passage : World.StrateManager->GetPassages())
{
if (Passage.LowerStrateIndex == CandidateStrateIndex
&& Passage.UpperStrateIndex + 1 == CandidateStrateIndex)
{
ArrivalPoint = Passage.LowerPoint;
++ArrivalCount;
}
if (Passage.UpperStrateIndex == CandidateStrateIndex
&& Passage.LowerStrateIndex == CandidateStrateIndex + 1)
{
DeparturePoint = Passage.UpperPoint;
++DepartureCount;
}
}
TestEqual(TEXT("corpus-free candidate has exactly one arrival mouth"), ArrivalCount, 1);
TestEqual(TEXT("corpus-free candidate has exactly one departure mouth"), DepartureCount, 1);
FVoxelStrateMeasureSettings MeasureSettings;
MeasureSettings.SampleStep = 4;
MeasureSettings.RadiusInVoxels = 256;
MeasureSettings.CenterXY = FVector2D::ZeroVector;
MeasureSettings.MaxCells = 8000000;
MeasureSettings.MaxRouteRetries = 16;
MeasureSettings.HeadroomCells = 2;
MeasureSettings.InteriorMarginVoxels = -1;
FArmStats CorpusBlend;
CorpusBlend.Name = TEXT("corpus blend (today)");
FArmStats Naive;
Naive.Name = TEXT("naive uniform");
FArmStats Constraint;
Constraint.Name = TEXT("constraint-sampled");
int32 RecipeDeterminismFailures = 0;
int32 ConstraintParameterViolations = 0;
FString FirstConstraintParameterViolation;
int32 ConstraintBoxStopCount = 0;
FString FirstConstraintBoxViolation;
bool bStopAfterConstraintViolation = false;
const double MeasurementStartSeconds = FPlatformTime::Seconds();
for (int32 CandidateIndex = 0;
CandidateIndex < NumCandidates && !bStopAfterConstraintViolation;
++CandidateIndex)
{
const FVoxelOpStackRecipe Recipe = VF_RollStrateStructure(
AuthoredSettings->Seed, CandidateIndex);
const FVoxelOpStackRecipe RepeatRecipe = VF_RollStrateStructure(
AuthoredSettings->Seed, CandidateIndex);
if (!VF_AreStrateStructureRecipesIdentical(Recipe, RepeatRecipe))
{
++RecipeDeterminismFailures;
}
FArmStats* Arms[] = { &CorpusBlend, &Naive, &Constraint };
const EVoxelStrateCorpusFreeSamplingMode Modes[] = {
EVoxelStrateCorpusFreeSamplingMode::NaiveUniform,
EVoxelStrateCorpusFreeSamplingMode::ConstraintSampled,
};
for (int32 ArmIndex = 0; ArmIndex < 3 && !bStopAfterConstraintViolation; ++ArmIndex)
{
FArmStats& Arm = *Arms[ArmIndex];
++Arm.Requested;
FVoxelStrateArchetypeParams CandidateParams;
bool bParameterRollsValid = true;
bool bConstraintParameterViolationThisCandidate = false;
for (int32 BlockIndex = 0; BlockIndex < UE_ARRAY_COUNT(GBlockSpecs); ++BlockIndex)
{
const FBlockSpec& Spec = GBlockSpecs[BlockIndex];
const int32 BlockSeed = AuthoredSettings->Seed
^ static_cast<int32>(Spec.SeedSalt);
FVoxelStrateRollInfo BlockRoll;
if (ArmIndex == 0)
{
BlockRoll = VF_RollStrateParamsDetailedForArchetype(
Corpus, Spec.Archetype, BlockSeed, CandidateIndex);
const FVoxelStrateRollInfo RepeatRoll = VF_RollStrateParamsDetailedForArchetype(
Corpus, Spec.Archetype, BlockSeed, CandidateIndex);
if (!BlockRoll.bValid || !RepeatRoll.bValid
|| !VF_AreStrateArchetypeParamsBitIdentical(
BlockRoll.ArchetypeParams, RepeatRoll.ArchetypeParams, Spec.Archetype))
{
++Arm.DeterminismFailures;
}
}
else
{
BlockRoll = VF_RollStrateParamsCorpusFree(
Spec.Archetype, BlockSeed, CandidateIndex, Modes[ArmIndex - 1],
StrateHeightInVoxels);
const FVoxelStrateRollInfo RepeatRoll = VF_RollStrateParamsCorpusFree(
Spec.Archetype, BlockSeed, CandidateIndex, Modes[ArmIndex - 1],
StrateHeightInVoxels);
if (!BlockRoll.bValid || !RepeatRoll.bValid
|| !VF_AreStrateArchetypeParamsBitIdentical(
BlockRoll.ArchetypeParams, RepeatRoll.ArchetypeParams, Spec.Archetype))
{
++Arm.DeterminismFailures;
}
if (ArmIndex == 2 && BlockRoll.bValid)
{
FString Violation;
if (!VF_ValidateStrateCorpusFreeConstraints(
Spec.Archetype, BlockRoll.ArchetypeParams,
StrateHeightInVoxels, Violation))
{
++ConstraintParameterViolations;
if (FirstConstraintParameterViolation.IsEmpty())
{
FirstConstraintParameterViolation = FString::Printf(
TEXT("candidate %d block %d (%s): %s"), CandidateIndex,
static_cast<int32>(Spec.Block),
VF_GetStrateArchetypeName(Spec.Archetype), *Violation);
}
bParameterRollsValid = false;
bConstraintParameterViolationThisCandidate = true;
}
}
}
if (!BlockRoll.bValid)
{
bParameterRollsValid = false;
++Arm.RollFailures;
Arm.RememberFailure(FString::Printf(
TEXT("candidate %d block %d roll failed: %s"), CandidateIndex,
static_cast<int32>(Spec.Block), *BlockRoll.FailureReason));
continue;
}
VF_CopyRollBlock(CandidateParams, BlockRoll, Spec.Archetype);
}
if (!bParameterRollsValid)
{
++Arm.Unmeasured;
if (ArmIndex == 2 && bConstraintParameterViolationThisCandidate)
{
// The constraint relation itself is a precondition of this arm; do not
// spend the remaining budget hiding a violated declared relation.
bStopAfterConstraintViolation = true;
}
continue;
}
VF_SetAllRuntimeBounds(CandidateParams, TopWorldZ, BottomWorldZ);
FVoxelOpStack Stack;
FVoxelOpContext Context;
FString BuildError;
const bool bBuilt = VF_BuildStackFromRecipe(
Recipe, CandidateParams, AuthoredSettings->Seed, 14.0f,
World.StrateManager.Get(), Stack, Context, &BuildError);
if (!bBuilt)
{
++Arm.BuildFailures;
++Arm.Unmeasured;
Arm.RememberFailure(FString::Printf(
TEXT("candidate %d build failed: %s"), CandidateIndex, *BuildError));
continue;
}
if (Stack.Num() != Recipe.Modifiers.Num() + 7)
{
++Arm.InvalidStackCount;
}
FString ChannelError;
if (!Stack.ValidateChannelOrder(&ChannelError))
{
++Arm.ChannelOrderFailures;
Arm.RememberFailure(FString::Printf(
TEXT("candidate %d channel order failed: %s"), CandidateIndex, *ChannelError));
}
if (Context.WorldRadiusVoxels != 0.0f)
{
++Arm.ContextViolations;
Arm.RememberFailure(FString::Printf(
TEXT("candidate %d changed WorldRadiusVoxels to %.9g"),
CandidateIndex, Context.WorldRadiusVoxels));
}
Stack.PrepareChunk(Context);
FStackDensitySampler Sampler(Stack);
const FVoxelStrateMetrics Metrics = VF_MeasureStrateWithSampler(
Sampler, BottomVoxelZ, TopVoxelZ + 1,
Context.EdgeSealThickness, MeasureSettings, nullptr);
if (!Metrics.bValid)
{
++Arm.Unmeasured;
Arm.RememberFailure(FString::Printf(
TEXT("candidate %d measurement refused: %s"), CandidateIndex,
*Metrics.RefusalReason));
continue;
}
Arm.AllQuality.Add(Metrics);
const bool bNonVacuous = Metrics.NumSampled > 0
&& Metrics.NumAir > 0 && Metrics.NumSolid > 0;
const bool bLargestEnough = bNonVacuous
&& Metrics.LargestComponentShare >= LargestComponentSurvivalThreshold;
bool bLawPass = false;
if (bLargestEnough)
{
FVoxelConnectivityDiagnostics Law;
if (ArrivalCount == 1 && DepartureCount == 1)
{
Law = VF_DiagnoseConnectivityWithSampler(
Sampler, BottomVoxelZ, TopVoxelZ + 1,
Context.EdgeSealThickness, ArrivalPoint, DeparturePoint, MeasureSettings);
++Arm.LawResults[VF_LawResultIndex(Law.Result)];
}
else
{
Law.bValid = false;
Law.Result = EVoxelConnectivityResult::OutOfWindow;
++Arm.LawResults[VF_LawResultIndex(Law.Result)];
}
bLawPass = ArrivalCount == 1 && DepartureCount == 1
&& Law.bValid && Law.Result == EVoxelConnectivityResult::Connected
&& !Law.bStartSnapped && !Law.bGoalSnapped;
if (bLawPass) { ++Arm.LawPasses; }
if (!bLawPass) { ++Arm.LawFailedObserved; }
}
else
{
++Arm.LawSkipped;
}
if (bNonVacuous) { ++Arm.NonVacuous; }
if (bLargestEnough) { ++Arm.LargestEnough; }
const bool bSurvivor = bNonVacuous && bLargestEnough && bLawPass;
if (bSurvivor)
{
++Arm.Survivors;
Arm.SurvivorQuality.Add(Metrics);
}
else if (!bNonVacuous)
{
++Arm.Vacuous;
}
else if (!bLargestEnough)
{
++Arm.Fragmented;
}
else
{
++Arm.LawFailed;
}
const FBoxVerdictReport BoxReport = VF_CheckStackBoxVerdicts(
Stack, Context, CandidateIndex, BottomVoxelZ, TopVoxelZ);
Arm.Box.Mixed += BoxReport.Mixed;
Arm.Box.AllSolid += BoxReport.AllSolid;
Arm.Box.AllAir += BoxReport.AllAir;
Arm.Box.Proved += BoxReport.Proved;
Arm.Box.CheckedVoxels += BoxReport.CheckedVoxels;
Arm.Box.Violations += BoxReport.Violations;
if (Arm.Box.FirstViolation.IsEmpty() && !BoxReport.FirstViolation.IsEmpty())
{
Arm.Box.FirstViolation = BoxReport.FirstViolation;
}
if (ArmIndex == 2 && BoxReport.Violations > 0)
{
++ConstraintBoxStopCount;
bStopAfterConstraintViolation = true;
FirstConstraintBoxViolation = BoxReport.FirstViolation;
}
}
}
const double MeasurementSeconds = FPlatformTime::Seconds() - MeasurementStartSeconds;
// Audit the same declared relations against the 12 current authored corpus vectors. Project
// assets carry their own height; the eight C++ defaults use the fixture's 4-chunk height.
int32 AuthoredConstraintViolations = 0;
FString AuthoredViolationList;
for (const FVoxelStrateCorpusEntry& Entry : Corpus.GetEntries())
{
float EntryHeight = 4.0f * (float)CHUNK_SIZE;
if (!Entry.SourcePath.Contains(TEXT("/VoxelForge/ComposerDefaults/")))
{
UVoxelStrateDefinition* Definition = LoadObject<UVoxelStrateDefinition>(
nullptr, *Entry.SourcePath);
if (Definition != nullptr)
{
EntryHeight = FMath::Max(1.0f, (float)Definition->StrateHeightInChunks)
* (float)CHUNK_SIZE;
}
}
FString Violation;
if (!VF_ValidateStrateCorpusFreeConstraints(
Entry.Archetype, Entry.ArchetypeParams, EntryHeight, Violation))
{
++AuthoredConstraintViolations;
if (!AuthoredViolationList.IsEmpty())
{
AuthoredViolationList += TEXT("; ");
}
AuthoredViolationList += FString::Printf(
TEXT("%s (%s, H=%.0f): %s"), *Entry.SourceName,
VF_GetStrateArchetypeName(Entry.Archetype), EntryHeight, *Violation);
}
}
const FArmStats* Arms[] = { &CorpusBlend, &Naive, &Constraint };
AddInfo(FString::Printf(
TEXT("Corpus-free measurement: requested %d candidates per arm; structure recipes were "
"shared across all three arms; sample step=%d radius=%d MaxCells=%d, fixed law mouths, "
"criterion precedence is vacuous > fragmented > law failed. Runtime %.3fs, roll/measure "
"%.3fs."),
NumCandidates, MeasureSettings.SampleStep, MeasureSettings.RadiusInVoxels,
MeasureSettings.MaxCells, FPlatformTime::Seconds() - TestStartSeconds, MeasurementSeconds));
FString Table = TEXT(
"approach | survival | vacuous | fragmented | law failed | air range | walkable range | feature scale range\n");
FString SurvivorTable = TEXT(
"survivor quality (ranges are over survivors only; main table ranges are over all measured candidates)\n"
"approach | survivors | air range (mean) | walkable range (mean) | feature scale range (mean)\n");
for (const FArmStats* Arm : Arms)
{
Table += FString::Printf(
TEXT("%s | %s | %d | %d | %d | %s | %s | %s\n"),
Arm->Name, *Arm->SurvivalText(), Arm->Vacuous, Arm->Fragmented, Arm->LawFailed,
*Arm->AllQuality.RangeText(Arm->AllQuality.AirMin, Arm->AllQuality.AirMax),
*Arm->AllQuality.RangeText(Arm->AllQuality.WalkableMin, Arm->AllQuality.WalkableMax),
*Arm->AllQuality.RangeText(Arm->AllQuality.FeatureMin, Arm->AllQuality.FeatureMax));
SurvivorTable += FString::Printf(
TEXT("%s | %d | %s | %s | %s\n"),
Arm->Name, Arm->SurvivorQuality.Count,
*Arm->SurvivorQuality.RangeText(Arm->SurvivorQuality.AirMin, Arm->SurvivorQuality.AirMax),
*Arm->SurvivorQuality.RangeText(Arm->SurvivorQuality.WalkableMin, Arm->SurvivorQuality.WalkableMax),
*Arm->SurvivorQuality.RangeText(Arm->SurvivorQuality.FeatureMin, Arm->SurvivorQuality.FeatureMax));
}
AddInfo(Table);
AddInfo(SurvivorTable);
for (const FArmStats* Arm : Arms)
{
AddInfo(FString::Printf(
TEXT("%s: non-vacuous=%d, largest>=%.2f=%d, law pass=%d, law failures observed=%d, "
"law skipped=%d, "
"roll failures=%d, build failures=%d, unmeasured=%d, deterministic roll failures=%d; "
"stack-count failures=%d, channel-order failures=%d, context violations=%d; "
"law results Connected=%d NotConnected=%d StartSolid=%d GoalSolid=%d OutOfWindow=%d Budget=%d; "
"quality all %s; survivor quality %s; first failure=%s."),
Arm->Name, Arm->NonVacuous, LargestComponentSurvivalThreshold, Arm->LargestEnough,
Arm->LawPasses, Arm->LawFailedObserved, Arm->LawSkipped,
Arm->RollFailures, Arm->BuildFailures,
Arm->Unmeasured, Arm->DeterminismFailures,
Arm->InvalidStackCount, Arm->ChannelOrderFailures, Arm->ContextViolations,
Arm->LawResults[0], Arm->LawResults[1], Arm->LawResults[2],
Arm->LawResults[3], Arm->LawResults[4], Arm->LawResults[5],
*Arm->AllQuality.SummaryText(), *Arm->SurvivorQuality.SummaryText(),
Arm->FirstFailure.IsEmpty() ? TEXT("none") : *Arm->FirstFailure));
AddInfo(FString::Printf(
TEXT("%s box verdicts: mixed=%d AllSolid=%d AllAir=%d proved=%d voxels checked=%lld "
"violations=%d; %s"),
Arm->Name, Arm->Box.Mixed, Arm->Box.AllSolid, Arm->Box.AllAir, Arm->Box.Proved,
Arm->Box.CheckedVoxels, Arm->Box.Violations,
Arm->Box.FirstViolation.IsEmpty() ? TEXT("no violations") : *Arm->Box.FirstViolation));
}
AddInfo(FString::Printf(
TEXT("Authored-corpus constraint audit: %d/%d vectors violated at their declared strate "
"height. %s"),
AuthoredConstraintViolations, Corpus.GetEntries().Num(),
AuthoredViolationList.IsEmpty() ? TEXT("none") : *AuthoredViolationList));
AddInfo(FString::Printf(
TEXT("Shared structure determinism failures=%d; per-arm channel/context violations are "
"reported below; constraint parameter violations=%d; constraint box-stop count=%d. %s%s"),
RecipeDeterminismFailures,
ConstraintParameterViolations, ConstraintBoxStopCount,
FirstConstraintParameterViolation.IsEmpty() ? TEXT("") : *FirstConstraintParameterViolation,
FirstConstraintBoxViolation.IsEmpty() ? TEXT("")
: *FString::Printf(TEXT("; first box violation: %s"), *FirstConstraintBoxViolation)));
TestEqual(TEXT("configured candidate count requested for corpus blend"), CorpusBlend.Requested, NumCandidates);
TestEqual(TEXT("configured candidate count requested for naive uniform"), Naive.Requested, NumCandidates);
TestEqual(TEXT("configured candidate count requested for constraint-sampled"), Constraint.Requested, NumCandidates);
TestEqual(TEXT("corpus blend roll failures"), CorpusBlend.RollFailures, 0);
TestEqual(TEXT("naive uniform roll failures"), Naive.RollFailures, 0);
TestEqual(TEXT("constraint-sampled roll failures"), Constraint.RollFailures, 0);
TestEqual(TEXT("corpus blend build failures"), CorpusBlend.BuildFailures, 0);
TestEqual(TEXT("naive uniform build failures"), Naive.BuildFailures, 0);
TestEqual(TEXT("constraint-sampled build failures"), Constraint.BuildFailures, 0);
TestEqual(TEXT("corpus-free parameter determinism failures"),
Naive.DeterminismFailures + Constraint.DeterminismFailures, 0);
TestEqual(TEXT("corpus parameter determinism failures"), CorpusBlend.DeterminismFailures, 0);
TestEqual(TEXT("shared structure recipe determinism failures"), RecipeDeterminismFailures, 0);
TestEqual(TEXT("constraint-sampled declared-parameter violations"),
ConstraintParameterViolations, 0);
TestEqual(TEXT("constraint-sampled box verdict violations"),
Constraint.Box.Violations, 0);
TestEqual(TEXT("corpus blend box verdict violations"), CorpusBlend.Box.Violations, 0);
TestEqual(TEXT("naive uniform box verdict violations"), Naive.Box.Violations, 0);
TestEqual(TEXT("corpus blend WorldRadiusVoxels/context violations"),
CorpusBlend.InvalidStackCount + CorpusBlend.ChannelOrderFailures
+ CorpusBlend.ContextViolations, 0);
TestEqual(TEXT("naive uniform WorldRadiusVoxels/context violations"),
Naive.InvalidStackCount + Naive.ChannelOrderFailures + Naive.ContextViolations, 0);
TestEqual(TEXT("constraint-sampled WorldRadiusVoxels/context violations"),
Constraint.InvalidStackCount + Constraint.ChannelOrderFailures
+ Constraint.ContextViolations, 0);
TestTrue(TEXT("constraint sampler did not stop early on a declared relation or box violation"),
!bStopAfterConstraintViolation);
const double TestSeconds = FPlatformTime::Seconds() - TestStartSeconds;
AddInfo(FString::Printf(TEXT("CorpusFree total runtime %.3fs; all three arms requested %d each; "
"WorldRadiusVoxels=0."), TestSeconds, NumCandidates));
return true;
}
#endif // WITH_DEV_AUTOMATION_TESTS
File diff suppressed because it is too large. Load diff
@@ -320,6 +320,35 @@ VOXELFORGE_API FVoxelStrateRollInfo VF_RollStrateParamsDetailedForArchetype(
/** Pure structure roll: no retained RNG state and no corpus/runtime dependency. */ /** Pure structure roll: no retained RNG state and no corpus/runtime dependency. */
VOXELFORGE_API FVoxelOpStackRecipe VF_RollStrateStructure(int32 Seed, int32 Index); VOXELFORGE_API FVoxelOpStackRecipe VF_RollStrateStructure(int32 Seed, int32 Index);
/**
* Provenance for a parameter block that does not read the authored corpus.
*
* NaiveUniform is the §3.3 control: every scalar is rolled independently inside the same
* finite code-declared envelope used by the constraint arm. ConstraintSampled draws the
* independent geometry quantities and derives the coupled quantities from generator equations.
* Neither mode reads a strate asset or a corpus entry.
*/
enum class EVoxelStrateCorpusFreeSamplingMode : uint8
{
NaiveUniform,
ConstraintSampled,
};
/** Roll one native parameter family without consulting FVoxelStrateCorpus. */
VOXELFORGE_API FVoxelStrateRollInfo VF_RollStrateParamsCorpusFree(
ECaveGeneratorType Archetype,
int32 Seed,
int32 Index,
EVoxelStrateCorpusFreeSamplingMode Mode,
float StrateHeightInVoxels);
/** Validate the hard geometry relations used by ConstraintSampled and the corpus audit. */
VOXELFORGE_API bool VF_ValidateStrateCorpusFreeConstraints(
ECaveGeneratorType Archetype,
const FVoxelStrateArchetypeParams& Params,
float StrateHeightInVoxels,
FString& OutViolation);
#if WITH_EDITOR #if WITH_EDITOR
/** /**
* Roll one candidate for the editor walk-through path. Parameter rolls call * Roll one candidate for the editor walk-through path. Parameter rolls call