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:
Fr0zkaandClaude Opus 5 committed 2026-09-04 08:32:01 +02:00
1 parent 358bd415a9
commit 190ff381c0
5 files changed
+1518 -264

No files matched your search

+91 -20
View File
@@ -200,18 +200,66 @@ exclusion list. Bools cannot be blended — roll them by probability or inherit
`Public/VoxelStrateComposer.h` + `Private/VoxelStrateComposer.cpp` now implement the corpus and
the deterministic roll. `Private/Tests/VoxelForgeComposerParameterRollTest.cpp` is the offline
measurement harness. The roll uses `FStrateGenerationParams::Lerp`, chooses 2–3 weighted parents
inside one archetype group, applies jitter of **±15% of each field's measured corpus range
(`max-min`)**, then applies reflected `ClampMin`/`ClampMax` metadata. It does not use clamps as
distribution ranges, does not touch generation, and leaves `WorldRadiusVoxels` at its default 0.
measurement harness. The roll uses the native family blend (`FStrateGenerationParams::Lerp` for
the tunnel family), chooses 2–3 weighted parents **inside one exact archetype group**, applies
jitter of **±15% of each field's measured corpus range (`max-min`)**, then applies reflected
`ClampMin`/`ClampMax` metadata. It does not use clamps as distribution ranges, does not touch
generation, and leaves `WorldRadiusVoxels` at its default 0. The sibling native parameter structs
are carried in the same corpus entry, so a `SurfaceWorld` vector is never blended with a
`TunnelNetwork` vector merely because both happen to be stored in a strate asset.
The loader walks the settings asset's fixed-strate and pool references, de-duplicates by asset path,
and accepts only `TunnelNetwork` / `Underwater`: those are the only archetypes whose authored vector
is `GenerationParams`. The current `DA_Settings` asset contains **one unique resolved reference**,
`DA_Strate3`, and it is `TunnelNetwork`; the other authored archetype assets are not in that
settings corpus. Consequently every measured field has zero spread, all 64 rolls repeat the same
known-good vector (parent weights still roll deterministically), and this run did not exercise
non-zero jitter. That is a corpus-size finding, not a rate optimization.
The original one-vector result was caused by the old loader following `DA_Settings`' fixed-strate
and pool references. An audit of those references finds **one unique path**,
`/Game/VoxelForge/DA_Strate3.DA_Strate3`, whose archetype is `TunnelNetwork`; that is why the
previous corpus only loaded one asset. The current loader instead asks the Asset Registry for every
project `UVoxelStrateDefinition`, sorts and de-duplicates the results, accepts `/Game` packages,
and ignores `Saved/Autosaves` and `Saved/Cooked` copies. The scan found **4 project assets**, with
no saved-copy, outside-project, duplicate, or unresolved records:
| Asset | Archetype |
|---|---|
| `DA_Strate1` | `SurfaceWorld` |
| `DA_Strate2` | `FloatingIslands` |
| `DA_Strate3` | `TunnelNetwork` |
| `DA_Strate4` | `TunnelNetwork` |
The hand-authored C++ defaults in `VoxelStrateTypes.h` are also corpus members: one for each of the
8 supported archetypes (`TunnelNetwork`, `FlatPlain`, `CrystalChamber`, `Maze`, `SurfaceWorld`,
`VerticalShafts`, `FloatingIslands`, `Underwater`). The fed corpus is therefore **12 members** in
**8 exact-archetype groups**. Group membership is `TunnelNetwork=3`, `FlatPlain=1`,
`CrystalChamber=1`, `Maze=1`, `SurfaceWorld=2`, `VerticalShafts=1`, `FloatingIslands=2`, and
`Underwater=1`.
The complete 253-descriptor spread table is emitted by the automation test, including sample count,
min/max/mean/standard deviation, excluded/reflected status, and clamp metadata. The 230 zero-range
rows are intentionally omitted below; these are the **23 rows with real spread** (float formatting
is the test's measured output):
| Archetype | Field | N | Min | Max | Mean | StdDev |
|---|---|---:|---:|---:|---:|---:|
| TunnelNetwork | MinRoomRadius | 3 | 10 | 25 | 15 | 7.07106781 |
| TunnelNetwork | MaxRoomRadius | 3 | 30 | 125 | 61.6666667 | 44.7834295 |
| TunnelNetwork | OriginRoomRadius | 3 | 20 | 120 | 53.3333333 | 47.1404521 |
| TunnelNetwork | TunnelMinRadius | 3 | 3 | 9 | 5 | 2.82842712 |
| TunnelNetwork | TunnelMaxRadius | 3 | 7 | 15 | 9.66666667 | 3.77123617 |
| SurfaceWorld | BaseGroundRelative | 2 | 0.200000003 | 0.300000012 | 0.250000007 | 0.0500000045 |
| SurfaceWorld | ElevationRange | 2 | 60 | 200 | 130 | 70 |
| SurfaceWorld | ContinentFrequency | 2 | 0.00100000005 | 0.00600000005 | 0.00350000005 | 0.0025 |
| SurfaceWorld | MountainStrength | 2 | 0.5 | 1 | 0.75 | 0.25 |
| SurfaceWorld | MountainFrequency | 2 | 0.00200000009 | 0.0120000001 | 0.0070000001 | 0.005 |
| SurfaceWorld | SurfaceRoughness | 2 | 2 | 3 | 2.5 | 0.5 |
| SurfaceWorld | ReliefStrength | 2 | 0 | 0.699999988 | 0.349999994 | 0.349999994 |
| SurfaceWorld | ReliefContrast | 2 | 1.60000002 | 2 | 1.80000001 | 0.199999988 |
| SurfaceWorld | TerraceHeight | 2 | 12 | 20 | 16 | 4 |
| SurfaceWorld | BeachWidth | 2 | 8 | 200 | 104 | 96 |
| FloatingIslands | IslandSpacing | 2 | 95 | 1000 | 547.5 | 452.5 |
| FloatingIslands | IslandDensity | 2 | 0.100000001 | 0.5 | 0.300000001 | 0.199999999 |
| FloatingIslands | IslandMinRadius | 2 | 18 | 50 | 34 | 16 |
| FloatingIslands | IslandMaxRadius | 2 | 42 | 500 | 271 | 229 |
| FloatingIslands | ThicknessRatio | 2 | 0.100000001 | 0.699999988 | 0.399999995 | 0.299999993 |
| FloatingIslands | VerticalJitter | 2 | 0.200000003 | 0.600000024 | 0.400000013 | 0.20000001 |
| FloatingIslands | TopFlatten | 2 | 0.600000024 | 0.800000012 | 0.700000018 | 0.099999994 |
| FloatingIslands | SurfaceRoughness | 2 | 0 | 4 | 2 | 2 |
The reflection/use audit found **36 excluded fields**: the 34 terrain-op transport fields
`TerraceStepHeight`, `TerraceHardness`, `TerraceNoiseDisplacement`, `LayerLineSpacing`,
@@ -222,22 +270,45 @@ The reflection/use audit found **36 excluded fields**: the 34 terrain-op transpo
`ChimneyMaxRadius`, `ChimneyHeight`, `DomeDensity`, `DomeMinRadius`, `DomeMaxRadius`,
`DomeHeightRatio`, `PinchDensity`, `PinchStrength`, `PinchLength` — populated per room by
`UVoxelTerrainOpDefinition` — plus the manager-owned runtime bounds `StrateTopWorldZ` and
`StrateBottomWorldZ`. The test prints the complete field-by-field table, including excluded fields,
`StrateBottomWorldZ`. The tunnel transport fields are measured directly from the native
`FStrateGenerationParams` X-macro even though they are not reflected `UPROPERTY`s; their values are
reported but deliberately not jittered. Same-named fields in sibling structs are not automatically
excluded: the exclusion applies to the tunnel transport slots whose generation source is the
terrain-op pool. The test prints the complete field-by-field table, including excluded fields,
reflection status, and clamp metadata.
The terrain-field source is now traced in code. `VoxelStrateManager.cpp` loads the active strate's
`TerrainOperations` soft-object pool, and `VoxelGenerator.cpp` passes that pool to
`VoxelCaveMorphology::BuildChunkCache`. For each nearest room, `FVoxelTerrainOpDefinition::ApplyTo`
overlays the selected operation onto a local copy (`FStrateGenerationParams LocalTerrainParams =
Params`) before the room operator runs. Thus the 34 values are transported through the strate
params, but their authored cave-detail values come from `UVoxelTerrainOpDefinition` assets selected
by the active strate's terrain-operation pool. Rolling those slots in the strate composer is not a
useful way to roll cave detail: selected room operations overwrite them. It is not a literal
absolute no-op, because the local base copy is still used where no operation overwrites a slot.
The correct future design is a **second corpus** over terrain-op assets plus the active pool's
membership/weights; this task does not build it. The project currently has **1**
`UVoxelTerrainOpDefinition` asset (the Asset Registry count used by the test).
The bool policy is explicit: `bTunnelsFlowTowardOrigin` is inherited from the dominant parent.
`OriginRoomMaxConnections` and `RoughnessNoiseType` retain `Lerp`'s existing SNAP behavior and
are not jittered. In this editor build clamp metadata was available while the table was built; it
is **not promised in a cooked runtime**, so the commandlet/cook-time bake remains owed.
The first 64-candidate run (step 4, radius 256, max 8,000,000 cells, fixture seed 1337) produced
64/64 non-vacuous candidates, 64/64 largest-component share ≥ 0.50, and 64/64 exact unsnapped
arrival→departure law passes. Every row was `air=0.520272`, `largest share=0.999996`,
`walkable=0.057995`, `feature scale=20.000000`, `Connected`; total runtime was **542.220 s**.
The §6.2 check saw **2,560 Mixed, 0 AllSolid, 0 AllAir, 0 brute-force voxels, 0 violations**:
this is explicitly **vacuous**, not evidence that rolled bounds are sound, because the current cave
box path emitted no uniform verdict. The test leaves §6.2 open and reports that fact rather than
turning it into a workaround.
The fed 64-candidate run (step 4 sweep, radius 256, max 8,000,000 cells, fixture seed 1337)
produced **59/64 survivors (92.2%)**. There were 60 non-vacuous candidates, 60 with largest
component share ≥ 0.50, 63 exact unsnapped arrival→departure law passes, and no roll failures.
The complete 64-row table is emitted by `VoxelForge.Composer.ParameterRoll`; each row reports
archetype, weighted parents, air fraction, largest share, walkable fraction, feature scale, and
arrival→departure verdict. The final full-suite run took **112.574 s** total (**0.015 s** corpus
load, **112.556 s** roll/measurement).
The §6.2 check is no longer vacuous: **1,224 boxes proved**, **1,629,144 voxels checked**,
**0 violations**. Its verdict mix was 1,336 Mixed, 549 AllSolid, and 675 AllAir; the law check
reported no violating candidate. The box law used unit step and brute-forced every voxel of each
uniform box, while the candidate sweep retained step 4. The test asserts zero violations and keeps
the law open to future changed candidates rather than treating this clean run as a license to tune
the survival rate.
### 3.4 The measurement pass — one grid, one flood fill, three jobs