feat(composer): Tier 4c -- a season that can actually ship, with a veto page

The composer could invent, measure and show strates. What did not exist was
an ARTEFACT a season could ship. Now it does.

VF_ComposeSeason: generate N candidates -> measure -> reject failures ->
select a descent sequence -> emit a JSON manifest + a review page.

⭐ THE PROPERTY THAT MATTERS: round-trip bit-identity. The manifest was
loaded, every selected stack rebuilt, and 393,216 density samples compared
BIT-IDENTICALLY. Re-composing the same seed yields byte-identical JSON.
A manifest that cannot reproduce its own world is worthless, and that is
the one thing that could not be assumed. Floats serialise as readable
values PLUS exact IEEE bits, so a season survives a text round trip.

Test season: 24 generated, 19 survived the hard gates, 6 selected.
18 not selected -- 4 vacuous, 1 primordial-law failure, 13 policy
non-selections. 4 grounded, 2 outliers. EVERY selected slot passes the
primordial law, asserted per slot rather than sampled.

Selection policy is explicit, named and PROVISIONAL, because Jahni has not
defined "good" yet and said so: "it'll be easier once i'm able to see the
strates candidates." So no quality score was invented and presented as
settled. The knobs are 75/25 grounded-to-outlier (outlier axis = distance
from corpus centroid), adjacent-similarity threshold .20 with penalty 1.0,
new-archetype bonus .10, new-recipe bonus .05, boss cadence every 5th
non-terminal slot. All of them are his to turn.

The veto is a review page in DESCENT ORDER -- the season read top to bottom
the way it is played, not the survivor/outlier ordering the candidate sheet
uses. Reuses VoxelStratePreview rather than a second renderer.

  Saved/VoxelForge/SeasonTest/season_0000_seed_9320/season_manifest.json
  .../review/index.html

⚠️ FIVE GAPS CODEX RAISED, recorded not buried:
1. §4's Tier A is strates PLUS passages; only the strate spine is persisted.
   Manager-generated passage geometry and content placement are not.
2. Authored fixed strates keep scalar vectors + asset paths, not every
   asset-level passage/biome/disturbance/content field.
3. Boss slots are MARKERS, not arena or content guarantees.
4. WorldRadiusVoxels == 0 is enforced as specced but contradicts a
   production bounded world -- Jahni's call, still unset by design.
5. Bit identity is proven for the same build and platform. A frozen season
   spanning engine versions needs an explicit schema contract. Real risk,
   since seasons freeze generation for months.

Build clean, 28 succeeded / 0 failed, 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-05 02:55:50 +02:00
1 parent 0b3ec18b43
commit 42e1f0ec8c
8 files changed
+3351 -5

No files matched your search

+16
View File
@@ -523,6 +523,21 @@ the caller centres it on each coarse survivor's `LargestComponentPoint` and the
sheet remains sorted by survivor status then descending corpus-centroid distance, repeats exact measurement sheet remains sorted by survivor status then descending corpus-centroid distance, repeats exact measurement
windows, marks rejection reasons, explains the 2D-connectivity limitation, and has no runtime generation hook. windows, marks rejection reasons, explains the 2D-connectivity limitation, and has no runtime generation hook.
**`Public/VoxelSeasonManifest.h` + `Private/VoxelSeasonManifest.cpp`** — Tier 4c's offline season
artifact. `VF_ComposeSeason` generates a bounded configurable candidate batch, applies the Tier 2
non-vacuous / largest-component / primordial-law gates, then selects an ordered Tier A spine with the
explicit provisional grounded/outlier, adjacency, variety, and five-slot boss policy. The JSON stores
the complete six-family parameter vector (float values carry both a readable number and exact IEEE-754
bits), seed/index/archetype/recipe, bounds, measured metrics, gate facts, candidate audit, and rejection
counts. `VF_LoadVoxelSeasonManifest` plus `VF_RebuildVoxelSeasonStrate` are the round-trip seam; generated
recipe entries rebuild through the mandatory-post builder, while authored `FixedStrates` retain their
absolute slot and native vector. The selected-only descent review reuses `VoxelStratePreview`, and all
composition/review work is editor/build-box code; normal runtime generation is untouched. The focused
season test currently reports **24 generated / 19 hard-gate survivors / 6 selected / 18 unselected or
rejected**, with **4 vacuous**, **1 primordial-law budget**, **13 policy-not-selected**, and **393,216**
round-trip density samples bit-identical. The specified UE 5.7 editor build succeeded and the full
`VoxelForge` namespace passed **28/28 tests** after this seam was added.
### 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;
**negative Strength = carve, positive = fill**) grouped by chunk in `TMap ChunkMods`. **negative Strength = carve, positive = fill**) grouped by chunk in `TMap ChunkMods`.
@@ -590,6 +605,7 @@ The plugin's first tests (`OPSTACK-PLAN.md` Phase 0.5). Run them from the editor
| ″ | `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. |
| `VoxelForgeComposerPromotionTest.cpp` | `VoxelForge.Composer.Promotion` | Re-measures the 12 project/default members, simulates five deterministic 24-candidate seasons with normalized measured-metric novelty (`<0.20`), cap 6, cumulative JSON promotion, provenance counts, corpus-hash checks, fresh-load gate verification, spread/survival reporting, and deliberate stale-metric corruption. Final run: **9 promoted**, corpus **4/8/9**, survival **20.8/54.2/54.2/45.8/50.0%**, spread **0.869214→0.916302**, **63.556 s**, 0 failures. | | `VoxelForgeComposerPromotionTest.cpp` | `VoxelForge.Composer.Promotion` | Re-measures the 12 project/default members, simulates five deterministic 24-candidate seasons with normalized measured-metric novelty (`<0.20`), cap 6, cumulative JSON promotion, provenance counts, corpus-hash checks, fresh-load gate verification, spread/survival reporting, and deliberate stale-metric corruption. Final run: **9 promoted**, corpus **4/8/9**, survival **20.8/54.2/54.2/45.8/50.0%**, spread **0.869214→0.916302**, **63.556 s**, 0 failures. |
| `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 centred on `LargestComponentPoint`, checks exact arrival→departure connectivity, rerolls every recipe/stack for determinism, and brute-forces every uniform box verdict. Final run: **42/64 survival (65.6%)**, **64 distinct recipes**, **0 invalid recipes**, **64 coarse filled + 64 coarse contour pairs**, **42 fine filled + 42 fine contour pairs**, blank plan/card **6/42→3/42**, **243.649 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 centred on `LargestComponentPoint`, checks exact arrival→departure connectivity, rerolls every recipe/stack for determinism, and brute-forces every uniform box verdict. Final run: **42/64 survival (65.6%)**, **64 distinct recipes**, **0 invalid recipes**, **64 coarse filled + 64 coarse contour pairs**, **42 fine filled + 42 fine contour pairs**, blank plan/card **6/42→3/42**, **243.649 s**, **0 refusals**. |
| `VoxelForgeComposerSeasonTest.cpp` | `VoxelForge.Composer.Season` | Composes a six-slot offline season from 24 structure candidates; asserts **19 hard-gate survivors**, **6 selected**, deterministic byte-identical JSON, absolute descent order, all selected primordial-law facts, and a selected-only review page with metrics/reasons/boss markers. Loads the JSON, rebuilds every selected stack, and compares **393,216** bounded density samples bit-for-bit. Rejection audit: **4 vacuous**, **1 primordial-law budget**, **13 policy-not-selected**. |
| `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. | | `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. |
+88 -4
View File
@@ -89,6 +89,64 @@ normal game path.** The editor-only PIE inspection button described below is a d
exception: it overlays one already-built slot so the owner can walk a measured candidate; it is not exception: it overlays one already-built slot so the owner can walk a measured candidate; it is not
cooked, does not compose a season, and is cleared by RebuildStrates. cooked, does not compose a season, and is cleared by RebuildStrates.
**Tier 4c is now the build-box seam:** `VF_ComposeSeason(SeasonSeed, Settings)` generates a bounded
candidate batch, measures every candidate, rejects vacuous/fragmented/primordial-law failures, and
writes the selected Tier A spine as diffable `season_manifest.json`. The default test budget is 24
candidates at measurement step 8 and a six-slot output; production can raise the candidate count for
the overnight budget without changing the runtime contract. The manifest stores each selected seed,
recipe, complete native parameter-family vector, archetype, bounds, measured metrics, and gate facts.
Floating-point fields carry both a readable value and their exact IEEE-754 bit pattern, so a loader can
rebuild the same stack and the season test can assert bit-identical density rather than assume it.
The selected-only review page is a descent-ordered `VoxelStratePreview` contact sheet. Each card shows
the depth, recipe, metrics, grounded/outlier/fixed selection reason, and boss-slot marker. Selection is
deliberately provisional: the current readable policy reserves 75% grounded and 25% outlier slots,
penalises near-identical neighbours, rewards new archetypes/recipes, and respects fixed absolute slots.
Jahni's preview and veto remain the decision that turns this artifact into a published season. The
composer and review writer are editor/build-box code only; no normal runtime generation path calls
them, and `WorldRadiusVoxels` remains 0.
An abbreviated manifest shape is:
```json
{
"format": "VoxelForgeSeasonManifest",
"schema_version": 1,
"season": 0,
"seed": 9320,
"candidate_count": 24,
"survivor_count": 19,
"selected_count": 6,
"strates": [
{
"depth_index": 0,
"seed": 123,
"archetype": "Maze",
"uses_recipe": true,
"recipe": {
"root_polarity": 0,
"root": { "op_class": 0, "param_block": 3 },
"shape_source": { "op_class": 3, "param_block": 3 },
"conversion": { "op_class": 8, "param_block": 3 },
"structural_param_block": 3,
"modifiers": []
},
"parameters": { "maze": { "LatticeSpacing": { "value": 18.0, "bits": "0x41900000" } } },
"measured_metrics": {
"air_fraction": { "kind": "float", "value": 0.5, "bits": "0x3F000000" },
"largest_component_share": { "kind": "float", "value": 0.9, "bits": "0x3F666666" }
},
"selection_reason": "grounded; provisional 75/25 policy",
"boss_slot": false
}
]
}
```
The real file is complete; the sample is intentionally abbreviated. `FVoxelSeasonFixedStrate` also
allows a build invocation to provide a full recipe/vector for an absolute slot, while entries adapted
from today's authored `FixedStrates` retain their native vector and source asset path.
### 3.2 Structure: how a stack gets assembled ### 3.2 Structure: how a stack gets assembled
The current shipping builders, for reference (structural posts included; TunnelNetwork's builder is The current shipping builders, for reference (structural posts included; TunnelNetwork's builder is
@@ -759,8 +817,15 @@ reviewable outlier quota instead of assuming survivor promotion preserves surpri
### ⭐ It is TWO things, not one ### ⭐ It is TWO things, not one
**Tier A — the SPINE. Finite, tiny, actually stored.** Strate slots and passages. ~30 strates plus a few **Tier A — the SPINE. Finite, tiny, actually stored.** Strate slots and passages. ~30 strates plus a few
passages each is roughly 100 entries — kilobytes. **This already exists** as `StrateLayout` plus the passages each is roughly 100 entries — kilobytes. The runtime manager already computes `StrateLayout`
passage list, computed once in `UVoxelStrateManager::Initialize`. and its passage list once in `UVoxelStrateManager::Initialize`; Tier 4c now adds the first reviewable
serialized Tier A artifact, `season_manifest.json`, containing the selected ordered strate spine and
the data required to reproduce each selected density field exactly.
The current Tier 4c manifest deliberately serializes the chosen strates, not the manager's generated
inter-strate passage geometry or the full material/creature/content placement payload. The season test
therefore proves each stored strate's density round trip; passage serialization and the wider content
manifest remain a follow-up before this file can be called the complete world manifest described here.
**Tier B — the FIELD. Effectively unbounded in XY, therefore NEVER stored.** Landmarks, rooms, material **Tier B — the FIELD. Effectively unbounded in XY, therefore NEVER stored.** Landmarks, rooms, material
deposits, build sites. It is a **function**: give it a region, it enumerates what is there. deposits, build sites. It is a **function**: give it a region, it enumerates what is there.
@@ -1038,8 +1103,16 @@ roughness and should stay late in the stack.
violations, plus 64 rendered preview pairs and an index.html contact sheet. The fine survivor ROI violations, plus 64 rendered preview pairs and an index.html contact sheet. The fine survivor ROI
is centred on `LargestComponentPoint` and labelled largest open space; the final before/after is centred on `LargestComponentPoint` and labelled largest open space; the final before/after
blank-plan count is 6/42 → 3/42. blank-plan count is 6/42 → 3/42.
- ⬜ Reject-and-resample driven by Tier 2 (§3.5) · the **offline season pipeline** with Jahni's review - ✅ **Season pipeline (§3.1, §3.5, §4, Tier 4c)** — `VF_ComposeSeason` generates and measures a
and veto (§3.1). bounded candidate batch, rejects vacuous/fragmented/primordial-law failures, and writes a Tier A
JSON spine plus a selected-only descent review page using `VoxelStratePreview`. The explicit policy
is provisional: default 75% grounded / 25% outlier, adjacent-similarity penalty, archetype/recipe
variety bonuses, and fixed absolute slots with boss markers every five non-terminal slots. The
focused artifact run was **24 generated / 19 hard-gate survivors / 6 selected / 18 rejected or
unselected**: 4 vacuous, 1 primordial-law budget failure, and 13 policy-not-selected. The loader
rebuild test compared **393,216 density samples bit-identically**, and the same seed emitted
byte-identical JSON. This closes the offline artifact and veto seam; full passage/content manifest
serialization is still open as noted in §4.
### Tier 5 — the long game ### Tier 5 — the long game
- ✅ **Promotion** (good strates rejoin the corpus) — JSON promotable records and season manifests, - ✅ **Promotion** (good strates rejoin the corpus) — JSON promotable records and season manifests,
@@ -1145,6 +1218,17 @@ in §3.3. The StructureRoll preview measured fine blank plan/card counts **6/42
42 fine filled pairs plus 42 contour pairs with 0 refusals. No existing generation/equivalence test 42 fine filled pairs plus 42 contour pairs with 0 refusals. No existing generation/equivalence test
failed. failed.
**2026-09-05 — Tier 4c season validation.** The specified UE 5.7 `VoxelMEditor Win64 Development`
build succeeded after adding the season manifest and review seam. The focused
`VoxelForge.Composer.Season` test passed, then the full headless `VoxelForge` namespace reported
**28 succeeded, 0 failed, 0 not run, 0 succeeded-with-warnings**. The season artifact used 24
generated candidates, 19 hard-gate survivors, and selected 6; it recorded 4 vacuous rejections, 1
primordial-law budget rejection, and 13 provisional-policy non-selections. Reloading the JSON and
rebuilding every selected stack compared **393,216 density samples bit-identically**; composing the
same seed twice emitted byte-identical JSON. The selected-only descent review was written beside the
manifest. The run also kept all existing equivalence, determinism, box-verdict, and connectivity
tests green.
--- ---
## 11. Tier 1 status — passages aim at real open space (bounded full-point query; build not run) ## 11. Tier 1 status — passages aim at real open space (bounded full-point query; build not run)
@@ -0,0 +1,208 @@
// Tier 4c — offline season composition, manifest determinism, and exact rebuild.
#if WITH_DEV_AUTOMATION_TESTS && WITH_EDITOR
#include "Misc/AutomationTest.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "VoxelDensityOpStack.h"
#include "VoxelSeasonManifest.h"
#include "VoxelStrateComposer.h"
namespace
{
bool SameDensityBits(float A, float B)
{
return FMemory::Memcmp(&A, &B, sizeof(float)) == 0;
}
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FVoxelForgeComposerSeasonTest,
"VoxelForge.Composer.Season",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
bool FVoxelForgeComposerSeasonTest::RunTest(const FString& Parameters)
{
(void)Parameters;
FVoxelStrateCorpus Corpus;
FString CorpusReport;
TestTrue(TEXT("season test loads a valid corpus"),
Corpus.LoadFromAssetRegistry(CorpusReport) && Corpus.IsValid());
if (!Corpus.IsValid())
{
AddError(CorpusReport);
return false;
}
FVoxelSeasonCompositionSettings Settings;
Settings.Corpus = &Corpus;
Settings.CandidateCount = 24;
Settings.SelectedStrateCount = 6;
Settings.StrateHeightInChunks = 4;
Settings.InterStrateGapChunks = 0;
Settings.OriginSpineRadius = 14.0f;
Settings.WorldRadiusVoxels = 0.0f;
Settings.OutputDirectory = FPaths::Combine(
FPaths::ProjectSavedDir(), TEXT("VoxelForge"), TEXT("SeasonTest"));
Settings.bWriteArtifacts = true;
Settings.bWriteReviewPage = true;
Settings.bIncludeCandidateAudit = true;
const int32 SeasonSeed = 0x2468;
const FVoxelSeasonManifest Manifest = VF_ComposeSeason(SeasonSeed, Settings);
TestTrue(TEXT("composition produces a usable season manifest"), Manifest.IsUsable());
if (!Manifest.IsUsable())
{
AddError(Manifest.Error);
return false;
}
TestEqual(TEXT("candidate count is the configured number of attempts"),
Manifest.CandidateCount, Settings.CandidateCount);
TestEqual(TEXT("selected count is the configured spine length"),
Manifest.SelectedCount, Settings.SelectedStrateCount);
TestEqual(TEXT("candidate audit contains every attempt"),
Manifest.CandidateAudit.Num(), Settings.CandidateCount);
TestEqual(TEXT("WorldRadiusVoxels remains zero in the artifact"),
Manifest.WorldRadiusVoxels, 0.0f);
TestEqual(TEXT("selected grounded/outlier counts add to generated slots"),
Manifest.GroundedSelectedCount + Manifest.OutlierSelectedCount,
Manifest.SelectedCount - Manifest.FixedSelectedCount);
for (int32 Index = 0; Index < Manifest.Strates.Num(); ++Index)
{
const FVoxelSeasonStrate& Strate = Manifest.Strates[Index];
TestEqual(FString::Printf(TEXT("slot %d keeps descent order"), Index),
Strate.DepthIndex, Index);
TestTrue(FString::Printf(TEXT("slot %d passed non-vacuous gate"), Index),
Strate.bPassedNonVacuous);
TestTrue(FString::Printf(TEXT("slot %d passed largest-component gate"), Index),
Strate.bPassedLargestComponent);
TestTrue(FString::Printf(TEXT("slot %d passed primordial law"), Index),
Strate.bPassedPrimordialLaw
&& Strate.PrimordialLawResult == EVoxelConnectivityResult::Connected);
TestTrue(FString::Printf(TEXT("slot %d has zero world radius"), Index),
Manifest.WorldRadiusVoxels == 0.0f);
}
TestTrue(TEXT("season manifest JSON was emitted"), !Manifest.SerializedJson.IsEmpty());
TestTrue(TEXT("season manifest file was emitted"), FPaths::FileExists(Manifest.ManifestPath));
TestTrue(TEXT("selected-season review page was emitted"),
FPaths::FileExists(Manifest.ReviewPagePath));
FString ReviewHtml;
if (FFileHelper::LoadFileToString(ReviewHtml, *Manifest.ReviewPagePath))
{
TestTrue(TEXT("review page explains why each selected slot was chosen"),
ReviewHtml.Contains(TEXT("Why selected:")));
TestTrue(TEXT("review page marks boss slots"), ReviewHtml.Contains(TEXT("Boss slot:")));
}
else
{
AddError(FString::Printf(TEXT("could not read review page %s"), *Manifest.ReviewPagePath));
}
// Compose again with the same inputs. SerializedJson excludes derived filesystem paths, so
// this is the byte-level determinism assertion for the portable artifact itself.
const FVoxelSeasonManifest RepeatManifest = VF_ComposeSeason(SeasonSeed, Settings);
TestTrue(TEXT("repeat composition is usable"), RepeatManifest.IsUsable());
TestEqual(TEXT("same season seed produces byte-identical JSON"),
RepeatManifest.SerializedJson, Manifest.SerializedJson);
FVoxelSeasonManifest LoadedManifest;
FString LoadReport;
TestTrue(TEXT("manifest loads from its JSON file"),
VF_LoadVoxelSeasonManifest(Manifest.ManifestPath, LoadedManifest, LoadReport));
if (!LoadedManifest.IsUsable())
{
AddError(LoadReport);
return false;
}
TestEqual(TEXT("loaded manifest has the same number of selected strates"),
LoadedManifest.Strates.Num(), Manifest.Strates.Num());
int64 ComparedDensitySamples = 0;
bool bAllDensityBitsIdentical = true;
for (int32 Index = 0; Index < Manifest.Strates.Num(); ++Index)
{
const FVoxelSeasonStrate& OriginalStrate = Manifest.Strates[Index];
const FVoxelSeasonStrate& LoadedStrate = LoadedManifest.Strates[Index];
TestTrue(FString::Printf(TEXT("slot %d parameter vector survives JSON bit-identically"), Index),
VF_AreStrateArchetypeParamsBitIdentical(
OriginalStrate.Params, LoadedStrate.Params, OriginalStrate.Archetype));
TestTrue(FString::Printf(TEXT("slot %d recipe survives JSON identically"), Index),
VF_AreStrateStructureRecipesIdentical(
OriginalStrate.Recipe, LoadedStrate.Recipe));
FVoxelOpStack OriginalStack;
FVoxelOpContext OriginalContext;
FVoxelOpStack LoadedStack;
FVoxelOpContext LoadedContext;
FString BuildError;
TestTrue(FString::Printf(TEXT("slot %d original stack rebuilds"), Index),
VF_RebuildVoxelSeasonStrate(
OriginalStrate, Manifest.OriginSpineRadius, nullptr,
OriginalStack, OriginalContext, &BuildError));
TestTrue(FString::Printf(TEXT("slot %d loaded stack rebuilds"), Index),
VF_RebuildVoxelSeasonStrate(
LoadedStrate, LoadedManifest.OriginSpineRadius, nullptr,
LoadedStack, LoadedContext, &BuildError));
if (OriginalStack.Num() == 0 || LoadedStack.Num() == 0)
{
bAllDensityBitsIdentical = false;
continue;
}
TestEqual(FString::Printf(TEXT("slot %d original/rebuilt stack operation count"), Index),
LoadedStack.Num(), OriginalStack.Num());
TestEqual(FString::Printf(TEXT("slot %d original context radius"), Index),
OriginalContext.WorldRadiusVoxels, 0.0f);
TestEqual(FString::Printf(TEXT("slot %d loaded context radius"), Index),
LoadedContext.WorldRadiusVoxels, 0.0f);
OriginalStack.PrepareChunk(OriginalContext);
LoadedStack.PrepareChunk(LoadedContext);
// Compare the complete bounded probe lattice for each selected field at two-voxel
// spacing, not a handful of favorable points. Every sampled scalar must retain its
// exact IEEE-754 bits after JSON load and stack reconstruction.
constexpr int32 ProbeHalfExtent = 32;
constexpr int32 ProbeStep = 2;
for (int32 Z = OriginalStrate.BottomWorldZ;
Z < OriginalStrate.TopWorldZ && bAllDensityBitsIdentical;
Z += ProbeStep)
{
for (int32 Y = -ProbeHalfExtent;
Y < ProbeHalfExtent && bAllDensityBitsIdentical;
Y += ProbeStep)
{
for (int32 X = -ProbeHalfExtent;
X < ProbeHalfExtent;
X += ProbeStep)
{
const float OriginalDensity = OriginalStack.EvalMC(
static_cast<float>(X), static_cast<float>(Y), static_cast<float>(Z));
const float LoadedDensity = LoadedStack.EvalMC(
static_cast<float>(X), static_cast<float>(Y), static_cast<float>(Z));
++ComparedDensitySamples;
if (!SameDensityBits(OriginalDensity, LoadedDensity))
{
bAllDensityBitsIdentical = false;
AddError(FString::Printf(
TEXT("slot %d density mismatch at (%d,%d,%d): 0x%08x vs 0x%08x"),
Index, X, Y, Z,
*reinterpret_cast<const uint32*>(&OriginalDensity),
*reinterpret_cast<const uint32*>(&LoadedDensity)));
break;
}
}
}
}
}
AddInfo(FString::Printf(TEXT("round-trip density probe compared %lld bit-identical samples"),
ComparedDensitySamples));
TestTrue(TEXT("manifest round-trip density field is bit-identical"),
bAllDensityBitsIdentical);
return true;
}
#endif
File diff suppressed because it is too large. Load diff
@@ -979,6 +979,18 @@ bool VF_WriteStratePreviewIndex(
SortedCandidates.Sort([](const FVoxelStratePreviewCandidate& A, SortedCandidates.Sort([](const FVoxelStratePreviewCandidate& A,
const FVoxelStratePreviewCandidate& B) const FVoxelStratePreviewCandidate& B)
{ {
if (A.bSeasonOrder != B.bSeasonOrder)
{
return A.bSeasonOrder;
}
if (A.bSeasonOrder && B.bSeasonOrder)
{
if (A.DepthIndex != B.DepthIndex)
{
return A.DepthIndex < B.DepthIndex;
}
return A.CandidateIndex < B.CandidateIndex;
}
if (A.bRejected != B.bRejected) if (A.bRejected != B.bRejected)
{ {
return !A.bRejected; return !A.bRejected;
@@ -1071,11 +1083,23 @@ bool VF_WriteStratePreviewIndex(
const FString CardClass = Candidate.bRejected ? TEXT("candidate rejected") : TEXT("candidate"); const FString CardClass = Candidate.bRejected ? TEXT("candidate rejected") : TEXT("candidate");
const FString MetricWindow = Candidate.Window.IsValid() const FString MetricWindow = Candidate.Window.IsValid()
? Candidate.Window.Describe() : WindowDescription; ? Candidate.Window.Describe() : WindowDescription;
const int32 DisplayIndex = Candidate.bSeasonOrder && Candidate.DepthIndex != INDEX_NONE
? Candidate.DepthIndex : Candidate.CandidateIndex;
Html += FString::Printf( Html += FString::Printf(
TEXT("<section class=\"%s\"><h2>#%d <span class=\"badge %s\">%s</span></h2>\n"), TEXT("<section class=\"%s\"><h2>#%d <span class=\"badge %s\">%s</span></h2>\n"),
*CardClass, Candidate.CandidateIndex, *StatusClass, *Status); *CardClass, DisplayIndex, *StatusClass, *Status);
Html += FString::Printf(TEXT("<div class=\"recipe\"><code>%s</code></div>\n"), Html += FString::Printf(TEXT("<div class=\"recipe\"><code>%s</code></div>\n"),
*VF_HtmlEscape(Candidate.RecipeString)); *VF_HtmlEscape(Candidate.RecipeString));
if (Candidate.bSeasonOrder)
{
Html += FString::Printf(
TEXT("<p class=\"reason\" style=\"color:#d7e3f4\"><strong>Why selected:</strong> %s"
"<br><strong>Boss slot:</strong> %s</p>\n"),
*VF_HtmlEscape(Candidate.SelectionReason.IsEmpty()
? TEXT("selected by the provisional season policy")
: Candidate.SelectionReason),
Candidate.bBossSlot ? TEXT("yes") : TEXT("no"));
}
if (Candidate.bRejected) if (Candidate.bRejected)
{ {
Html += FString::Printf(TEXT("<p class=\"reason\"><strong>Why rejected:</strong> %s</p>\n"), Html += FString::Printf(TEXT("<p class=\"reason\"><strong>Why rejected:</strong> %s</p>\n"),
@@ -0,0 +1,269 @@
// Offline Tier 4c season composition and the diffable Tier A manifest.
//
// This header deliberately describes an editor/build-box artifact. The normal runtime path still
// receives its existing settings and builds density exactly as before; no runtime code consumes a
// season manifest as a hidden generation fallback.
#pragma once
#include "CoreMinimal.h"
#include "VoxelStrateComposer.h"
#include "VoxelStrateMeasure.h"
class UVoxelSettings;
/** Why the provisional season selector put one surviving candidate in a slot. */
enum class EVoxelSeasonSelectionReason : uint8
{
Grounded,
Outlier,
Fixed,
};
/**
* Explicit, reviewable selection knobs for one season.
*
* None of these values is a settled definition of quality. They are the first policy Jahni can
* change after walking the review page. Hard validation remains separate from this policy.
*/
struct VOXELFORGE_API FVoxelSeasonSelectionPolicy
{
// Placeholder mix only: ordinary strates give an outlier contrast. Revisit after the first
// season review; this is not a learned or final quality score.
float GroundedFraction = 0.75f;
float OutlierFraction = 0.25f;
// Below this measured distance two neighbours look too similar. The selector subtracts a
// penalty proportional to the gap; provisional because the useful contrast is visual.
double AdjacentSimilarityDistanceThreshold = 0.20;
double AdjacentSimilarityPenaltyWeight = 1.0;
// Rewards an unseen archetype/recipe in the already chosen prefix. These are variety
// tie-breakers, not claims that one archetype or recipe is better. Provisional review knobs.
double NewArchetypeBonus = 0.10;
double NewRecipeBonus = 0.05;
// GDD §6 says roughly every five levels, and a boss is intermediary rather than terminal.
// This interval is therefore an explicit provisional layout knob, not a quality score.
int32 BossSlotInterval = 5;
// Tier 2 hard gate: a surviving air component must retain at least this share. Provisional
// only in its threshold; the existence of the gate is non-negotiable for a shippable season.
float MinimumLargestComponentShare = 0.50f;
// Normalizers for the two unbounded measured dimensions in the adjacency distance. Fractions
// already live in [0,1]. These physical scales are provisional and visible in the manifest.
float FeatureScaleNormalizationVoxels = 256.0f;
float ClearanceNormalizationVoxels = 64.0f;
};
/** A caller-supplied fixed slot for tests/tools that already have a complete manifest record. */
struct VOXELFORGE_API FVoxelSeasonFixedStrate
{
int32 Seed = 0;
int32 HeightInChunks = 4;
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
FVoxelStrateArchetypeParams Params;
FVoxelOpStackRecipe Recipe;
bool bUsesRecipe = true;
FString SourceDefinitionPath;
};
/** Settings for the offline build-box pass. No member is consulted by normal runtime generation. */
struct VOXELFORGE_API FVoxelSeasonCompositionSettings
{
// Optional live-world settings. TotalStrates, spine/gap values, and FixedStrates are read
// from it when present. The pointer is borrowed for the duration of VF_ComposeSeason only.
const UVoxelSettings* WorldSettings = nullptr;
// Optional preloaded corpus. With nullptr the composer loads the project asset corpus on the
// build box, exactly like the existing offline composer tests.
const FVoxelStrateCorpus* Corpus = nullptr;
// 24 gives a useful minute-scale automation pass at the default coarse measurement window.
// Production can raise this to the overnight budget without changing the policy.
int32 CandidateCount = 24;
int32 SelectedStrateCount = 10;
// Generated candidates use one stable height. Fixed authored slots may provide their own
// height. World layout bounds are written into every selected parameter family below.
int32 StrateHeightInChunks = 4;
int32 InterStrateGapChunks = 0;
float OriginSpineRadius = 14.0f;
float WorldRadiusVoxels = 0.0f;
// Coarse measurement is deliberately bounded. Tests may reduce the window; production may
// spend the overnight budget on a finer pass after the selector has been judged.
FVoxelStrateMeasureSettings MeasureSettings;
FVoxelSeasonSelectionPolicy SelectionPolicy;
// Explicit composed fixed slots take precedence. When empty, the composer adapts the
// existing UVoxelSettings::FixedStrates map, preserving its absolute slot indices.
TMap<int32, FVoxelSeasonFixedStrate> FixedStrates;
// Empty means ProjectSavedDir()/VoxelForge/Seasons. Files are never written to the runtime
// content directory by this API.
FString OutputDirectory;
bool bWriteArtifacts = true;
bool bWriteReviewPage = true;
bool bIncludeCandidateAudit = true;
// Informational only; when WorldSettings is present CurrentSeason supplies the default.
int32 SeasonNumber = 0;
FVoxelSeasonCompositionSettings()
{
// The existing Tier 4b test uses step 4. Season composition defaults to step 8 so the
// default automation artifact is measured in minutes, while MaxCells remains bounded.
MeasureSettings.SampleStep = 8;
MeasureSettings.RadiusInVoxels = 256;
MeasureSettings.MaxCells = 8000000;
MeasureSettings.MaxRouteRetries = 16;
MeasureSettings.HeadroomCells = 2;
MeasureSettings.InteriorMarginVoxels = -1;
}
};
// Friendly aliases for callers that use the task's shorter naming.
using FVoxelSeasonComposerSettings = FVoxelSeasonCompositionSettings;
using FVoxelSeasonComposeSettings = FVoxelSeasonCompositionSettings;
/** Compact audit row; the full vectors live only for selected strates. */
struct VOXELFORGE_API FVoxelSeasonCandidateAudit
{
int32 CandidateIndex = INDEX_NONE;
int32 Seed = 0;
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
uint32 RecipeHash = 0;
double DistanceFromCorpusCentroid = 0.0;
bool bPassedHardGates = false;
bool bSelected = false;
FString RejectionReason;
};
/** Count of one explicit hard-gate or final-placement rejection reason. */
struct VOXELFORGE_API FVoxelSeasonRejectionCount
{
FString Reason;
int32 Count = 0;
};
/** One ordered Tier A spine entry. */
struct VOXELFORGE_API FVoxelSeasonStrate
{
// DepthIndex is the absolute world-layout slot. CandidateIndex is the offline attempt id.
int32 DepthIndex = INDEX_NONE;
int32 CandidateIndex = INDEX_NONE;
int32 Seed = 0;
int32 HeightInChunks = 0;
int32 TopWorldZ = 0;
int32 BottomWorldZ = 0;
ECaveGeneratorType Archetype = ECaveGeneratorType::TunnelNetwork;
FVoxelOpStackRecipe Recipe;
FVoxelStrateArchetypeParams Params;
FVoxelStrateMeasuredMetrics Metrics;
double DistanceFromCorpusCentroid = 0.0;
FString SelectionReason;
FString SourceDefinitionPath;
bool bGrounded = false;
bool bOutlier = false;
bool bBossSlot = false;
bool bFixed = false;
bool bUsesRecipe = true;
bool bPassedNonVacuous = false;
bool bPassedLargestComponent = false;
bool bPassedPrimordialLaw = false;
EVoxelConnectivityResult PrimordialLawResult = EVoxelConnectivityResult::OutOfWindow;
};
/**
* The stored Tier A season artifact.
*
* Tier B is intentionally absent: its hash-lattice field remains an arithmetic/enumeration API,
* not a second stored table. Strates is ordered from the surface down and is the only finite spine
* persisted by this task.
*/
struct VOXELFORGE_API FVoxelSeasonManifest
{
static constexpr int32 CurrentSchemaVersion = 1;
bool bValid = false;
int32 SchemaVersion = CurrentSchemaVersion;
FString Error;
int32 Season = 0;
int32 Seed = 0;
uint32 InputCorpusHash = 0;
int32 CandidateCount = 0;
int32 SurvivorCount = 0;
int32 SelectedCount = 0;
int32 RejectedCount = 0;
int32 GroundedSelectedCount = 0;
int32 OutlierSelectedCount = 0;
int32 FixedSelectedCount = 0;
int32 TotalStrates = 0;
int32 InterStrateGapChunks = 0;
float OriginSpineRadius = 0.0f;
float WorldRadiusVoxels = 0.0f;
FVoxelSeasonSelectionPolicy SelectionPolicy;
FString PolicyNote;
TArray<FVoxelSeasonStrate> Strates;
TArray<FVoxelSeasonCandidateAudit> CandidateAudit;
TArray<FVoxelSeasonRejectionCount> RejectionCounts;
// Derived artifact locations; intentionally not serialized into the portable JSON.
FString ManifestPath;
FString ReviewPagePath;
FString SerializedJson;
bool IsUsable() const
{
return bValid && Error.IsEmpty() && WorldRadiusVoxels == 0.0f
&& Strates.Num() == SelectedCount && SelectedCount > 0;
}
};
/** Human-readable enum names used by JSON and the review page. */
VOXELFORGE_API const TCHAR* VF_GetVoxelSeasonSelectionReasonName(
EVoxelSeasonSelectionReason Reason);
/** Compose, validate, select, serialize, and (by default) write one season. Editor/build-box only. */
VOXELFORGE_API FVoxelSeasonManifest VF_ComposeSeason(
int32 SeasonSeed, const FVoxelSeasonCompositionSettings& Settings);
/** Convenience overload that adapts the existing runtime settings asset. */
VOXELFORGE_API FVoxelSeasonManifest VF_ComposeSeason(
int32 SeasonSeed, const UVoxelSettings* WorldSettings);
/** Serialize/read the complete Tier A manifest as stable pretty JSON. */
VOXELFORGE_API FString VF_SerializeVoxelSeasonManifest(
const FVoxelSeasonManifest& Manifest);
VOXELFORGE_API bool VF_SaveVoxelSeasonManifest(
const FString& ManifestPath, const FVoxelSeasonManifest& Manifest, FString& OutReport);
VOXELFORGE_API bool VF_LoadVoxelSeasonManifest(
const FString& ManifestPath, FVoxelSeasonManifest& OutManifest, FString& OutReport);
/** Rebuild exactly the stack represented by one manifest entry. */
VOXELFORGE_API bool VF_BuildSeasonStrateStack(
const FVoxelSeasonStrate& Strate,
float SpineRadius,
const UVoxelStrateManager* StrateManager,
FVoxelOpStack& OutStack,
FVoxelOpContext& OutContext,
FString* OutError = nullptr);
/** Verbose alias for tests/tools that want the round-trip operation named explicitly. */
VOXELFORGE_API bool VF_RebuildVoxelSeasonStrate(
const FVoxelSeasonStrate& Strate,
float SpineRadius,
const UVoxelStrateManager* StrateManager,
FVoxelOpStack& OutStack,
FVoxelOpContext& OutContext,
FString* OutError = nullptr);
@@ -18,6 +18,16 @@ class UVoxelSettings;
class UVoxelStrateDefinition; class UVoxelStrateDefinition;
class UVoxelStrateManager; class UVoxelStrateManager;
#if WITH_EDITOR
// The complete definitions live in VoxelSeasonManifest.h. Keeping this forward declaration here
// makes the Tier 4c entry point discoverable from the composer header without creating a circular
// include (the manifest header includes this one for the existing vector/recipe types).
struct FVoxelSeasonManifest;
struct FVoxelSeasonCompositionSettings;
VOXELFORGE_API FVoxelSeasonManifest VF_ComposeSeason(
int32 SeasonSeed, const FVoxelSeasonCompositionSettings& Settings);
#endif
/** The one-bit identity choice at the root of a rolled structure. */ /** The one-bit identity choice at the root of a rolled structure. */
UENUM(BlueprintType) UENUM(BlueprintType)
enum class EVoxelStrateRootPolarity : uint8 enum class EVoxelStrateRootPolarity : uint8
@@ -71,6 +71,12 @@ struct VOXELFORGE_API FVoxelStrateFinePreviewSettings
struct VOXELFORGE_API FVoxelStratePreviewCandidate struct VOXELFORGE_API FVoxelStratePreviewCandidate
{ {
int32 CandidateIndex = INDEX_NONE; int32 CandidateIndex = INDEX_NONE;
// Season pages deliberately opt into descent order. The ordinary candidate contact sheet
// keeps its survivor/outlier ordering; a season review must read top-to-bottom like play.
bool bSeasonOrder = false;
int32 DepthIndex = INDEX_NONE;
FString SelectionReason;
bool bBossSlot = false;
FVoxelStratePreviewWindow Window; FVoxelStratePreviewWindow Window;
FString RecipeString; FString RecipeString;
FString ArrivalDepartureVerdict; FString ArrivalDepartureVerdict;