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:
1 parent
0b3ec18b43
commit
42e1f0ec8c
8 files changed
+3351
-5
No files matched your search
+16
@@ -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
|
||||
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`
|
||||
`UVoxelDiffLayer : UObject` (h:77). Stores `FVoxelModification` (h:43: Center/Radius/Strength;
|
||||
**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. |
|
||||
| `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**. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
|
||||
+88
-4
@@ -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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
**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
|
||||
passage list, computed once in `UVoxelStrateManager::Initialize`.
|
||||
passages each is roughly 100 entries — kilobytes. The runtime manager already computes `StrateLayout`
|
||||
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
|
||||
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
|
||||
is centred on `LargestComponentPoint` and labelled largest open space; the final before/after
|
||||
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
|
||||
and veto (§3.1).
|
||||
- ✅ **Season pipeline (§3.1, §3.5, §4, Tier 4c)** — `VF_ComposeSeason` generates and measures a
|
||||
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
|
||||
- ✅ **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
|
||||
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)
|
||||
|
||||
@@ -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,
|
||||
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)
|
||||
{
|
||||
return !A.bRejected;
|
||||
@@ -1071,11 +1083,23 @@ bool VF_WriteStratePreviewIndex(
|
||||
const FString CardClass = Candidate.bRejected ? TEXT("candidate rejected") : TEXT("candidate");
|
||||
const FString MetricWindow = Candidate.Window.IsValid()
|
||||
? Candidate.Window.Describe() : WindowDescription;
|
||||
const int32 DisplayIndex = Candidate.bSeasonOrder && Candidate.DepthIndex != INDEX_NONE
|
||||
? Candidate.DepthIndex : Candidate.CandidateIndex;
|
||||
Html += FString::Printf(
|
||||
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"),
|
||||
*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)
|
||||
{
|
||||
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 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. */
|
||||
UENUM(BlueprintType)
|
||||
enum class EVoxelStrateRootPolarity : uint8
|
||||
|
||||
@@ -71,6 +71,12 @@ struct VOXELFORGE_API FVoxelStrateFinePreviewSettings
|
||||
struct VOXELFORGE_API FVoxelStratePreviewCandidate
|
||||
{
|
||||
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;
|
||||
FString RecipeString;
|
||||
FString ArrivalDepartureVerdict;
|
||||
|
||||
Reference in new issue
Block a user