feat(composer): the parameter roll -- infrastructure real, corpus starved

Tier 4a. The first piece of the composer that INVENTS rather than fixes.

The design, from COMPOSER-NOTES §3.3: do NOT roll 73 parameters
independently. The coherent region of a 73-dimensional space is a
vanishingly thin sheet and uniform sampling never lands on it -- and
independent rolling destroys the CORRELATIONS (room spacing <-> room
radius <-> tunnel length) that make a vector coherent. So blend known-good
vectors through the existing FStrateGenerationParams::Lerp instead:
constraint satisfaction is free, because a convex combination preserves
every relation holding in both parents. Min <= Max stays true because it
was never broken.

Built: FVoxelStrateCorpus (loads authored vectors, computes per-field
spread), VF_RollStrateParams (2-3 weighted parents, convex blend, jitter
by +-15% of each field's MEASURED corpus range -- not by the clamps, which
are "don't type nonsense" limits, not "this makes a good world" limits),
and VoxelForge.Composer.ParameterRoll. Determinism asserted by rolling
twice and comparing every field.

Exclusion list found by reflection, not assumption: StrateTopWorldZ and
StrateBottomWorldZ (manager-owned runtime bounds) plus 34 terrain-op
transport fields (TerraceStepHeight..PinchLength) that are copied in from
room operators rather than being strate tunables. Bools inherit from the
dominant parent; ints/enums use Lerp's existing snap.

⚠️ THE RESULT IS VACUOUS AND SAYS SO. 64/64 candidates "survived" -- but
the corpus loaded ONE vector (DA_Strate3), so all 64 are bit-identical.
100% survival of a single point is not evidence of anything. The box-
verdict check is vacuous too: 2,560 boxes, 0 proved, 0 voxels brute-forced,
so §6.2 is NOT closed. The test emits a warning saying exactly this rather
than reporting a clean pass, and no workaround was added to hide it.

Four strate assets exist (DA_Strate1..4); the loader found one. Fixing the
corpus is the next task -- with one parent there is nothing to blend, and
every number above is a placeholder.

Also owed: clamp metadata is editor-only, so the safety net needs a
cook-time baked table before this ships.

Nothing calls the composer from any runtime path (§3.1: offline, once per
season, on a build box).

Build clean, 23 tests / 0 failed (1 carries the deliberate vacuity
warning), verified in VoxelM.log.

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 07:19:11 +02:00
1 parent 8e346a3054
commit 358bd415a9
5 files changed
+1649 -2

No files matched your search

+48 -2
View File
@@ -196,6 +196,49 @@ small commandlet walking the struct by reflection. Clamp coverage is partial. A
tunables at all (`StrateTopWorldZ` / `StrateBottomWorldZ` are runtime Z bounds) and need an explicit
exclusion list. Bools cannot be blended — roll them by probability or inherit from the dominant parent.
### ✅ Tier 4a status — parameter roll built (2026-09-04)
`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.
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 reflection/use audit found **36 excluded fields**: the 34 terrain-op transport fields
`TerraceStepHeight`, `TerraceHardness`, `TerraceNoiseDisplacement`, `LayerLineSpacing`,
`LayerLineDepth`, `OverhangStrength`, `OverhangDepth`, `OverhangFrequency`, `RibbingSpacing`,
`RibbingDepth`, `CliffStrength`, `ScallopStrength`, `ScallopFrequency`, `ArchDensity`,
`ArchMinRadius`, `ArchMaxRadius`, `ColumnDensity`, `ColumnMinRadius`, `ColumnMaxRadius`,
`PitDensity`, `PitMinRadius`, `PitMaxRadius`, `PitDepth`, `ChimneyDensity`, `ChimneyMinRadius`,
`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,
reflection status, and clamp metadata.
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.
### 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
@@ -533,8 +576,11 @@ an open semantic decision; the Part 0 analysis above concludes that FloorBias is
roughness and should stay late in the stack.
### Tier 4 — the composer proper
Structure roll (§3.2) · parameter roll (§3.3) · reject-and-resample driven by Tier 2 (§3.5) · the
**offline season pipeline** with Jahni's review and veto (§3.1).
- ✅ **Parameter roll (§3.3, Tier 4a)** — corpus spread + deterministic blend/jitter implementation
and the 64-candidate measurement pass are built; the first run exposed a one-vector settings corpus
and a vacuous cave box-verdict scan, both recorded above.
- ⬜ Structure roll (§3.2) · reject-and-resample driven by Tier 2 (§3.5) · the **offline season
pipeline** with Jahni's review and veto (§3.1).
### Tier 5 — the long game
**Promotion** (good strates rejoin the corpus) · theme and tag draws for materials, creatures and audio ·