I thought the fitted mouth-AABB window was cropping an origin-routed path and producing a false NotConnected. Codex tested it three ways at step 1 on the Maze candidate Jahni walked: mouth-AABB + margin 842,996 cells NotConnected origin-inclusive 3,251,556 cells NotConnected WHOLE STRATE 31,881,528 cells NotConnected 115,578 fit cells, 2,903 components, largest 11,418 -- and the mouths sit in components of 172 and FOUR cells, 274 voxels apart. The window was never the problem. Sixth measurement hypothesis, first one that was wrong in the other direction: this time the instrument was fine and I doubted it. The window rule still improved: bIncludeOriginInCoverWindow now covers the route an origin-rooted topology actually implies (Maze, VerticalShafts, TunnelNetwork, Underwater), expansion happens BEFORE MaxCells, and an over-cap window is REFUSED honestly instead of silently cropping back to a mouth-only box. VerticalShafts refused at the 4M production cap and said so. Corrected law across eight: 0/7 measured Connected, 1 unknown (CoarseLiedBudgetExhausted), 1 refused. ⇒ FIVE CAUSES NOW ELIMINATED: size (Jahni walked it), the fit stencil (fixed, 309 pockets -> 96), placement (mouths land on legal poses), corridor connectivity (spanning tree, 0 disconnected cells over 64 seeds), and the measurement window (whole-strate says the same thing). ⭐ AND THE REMAINING CAUSE IS NAMED BY ITS OWN NUMBER: the departure mouth sits in a FOUR-CELL player-fit component. A passage tube is a bore through rock -- sloped or vertical, no floor within the walkable angle -- so the mouth point is not a standing position and nothing around it is either. You arrive and there is nowhere to stand. That is precisely COMPOSER-NOTES §6.5 / Tier 1's unbuilt item: "the passage mouth becomes a room plus a distinctive landmark". It was never started. The law cannot pass while a mouth opens into a bore instead of a landing. Build clean, 32/32 successful, 0 failed, same two known warnings. WorldRadiusVoxels=0, lateral gating, determinism, equivalence all intact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K8V9my1PL68pmQb2gutZZV
102 lines
3.7 KiB
C++
102 lines
3.7 KiB
C++
// Shared measurement-window policy for player-fit diagnostics.
|
|
//
|
|
// This is deliberately test/editor-side policy. The density generators do not know which two
|
|
// points a measurement is asking about, so the caller that knows the archetype must declare
|
|
// whether its topology can require the origin spine.
|
|
|
|
#pragma once
|
|
|
|
#if WITH_DEV_AUTOMATION_TESTS
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "VoxelStrateMeasure.h"
|
|
#include "VoxelStrateTypes.h"
|
|
|
|
namespace VoxelForgePlayerFitWindow
|
|
{
|
|
/**
|
|
* The four families below have an origin-rooted connectivity implication:
|
|
*
|
|
* - Maze: every lattice parent chain terminates at (0,0,0).
|
|
* - VerticalShafts: every drainage-tree parent chain terminates at the origin spine.
|
|
* - TunnelNetwork/Underwater: their origin-flowing room graph terminates at the origin hub.
|
|
*
|
|
* Slabs, SurfaceWorld, and FloatingIslands have local/independent interior topology for
|
|
* this law. The global structural spine is still generated for them, but it is not the
|
|
* topology that their mouth-to-mouth fit law promises to follow.
|
|
*/
|
|
inline bool NeedsOriginInWindow(ECaveGeneratorType Archetype)
|
|
{
|
|
switch (Archetype)
|
|
{
|
|
case ECaveGeneratorType::Maze:
|
|
case ECaveGeneratorType::VerticalShafts:
|
|
case ECaveGeneratorType::TunnelNetwork:
|
|
case ECaveGeneratorType::Underwater:
|
|
return true;
|
|
case ECaveGeneratorType::FlatPlain:
|
|
case ECaveGeneratorType::CrystalChamber:
|
|
case ECaveGeneratorType::SurfaceWorld:
|
|
case ECaveGeneratorType::FloatingIslands:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inline const TCHAR* RouteWindowName(ECaveGeneratorType Archetype)
|
|
{
|
|
return NeedsOriginInWindow(Archetype)
|
|
? TEXT("origin-inclusive mouth-AABB + margin")
|
|
: TEXT("mouth-AABB + margin");
|
|
}
|
|
|
|
inline FString DescribeResolvedWindow(
|
|
const FVoxelStrateMetrics& Metrics,
|
|
int32 SampleStep,
|
|
ECaveGeneratorType Archetype)
|
|
{
|
|
if (!Metrics.bValid)
|
|
{
|
|
return FString::Printf(
|
|
TEXT("%s step=%d REFUSED: %s"),
|
|
RouteWindowName(Archetype),
|
|
SampleStep,
|
|
Metrics.RefusalReason.IsEmpty() ? TEXT("unknown") : *Metrics.RefusalReason);
|
|
}
|
|
return FString::Printf(
|
|
TEXT("%s step=%d X[%.1f,%.1f) Y[%.1f,%.1f) Z[%d,%d) "
|
|
"grid=%dx%dx%d cells=%lld"),
|
|
RouteWindowName(Archetype),
|
|
SampleStep,
|
|
Metrics.SampledMinX,
|
|
Metrics.SampledMaxX,
|
|
Metrics.SampledMinY,
|
|
Metrics.SampledMaxY,
|
|
Metrics.SampledMinZ,
|
|
Metrics.SampledMaxZ,
|
|
Metrics.SampledNumX,
|
|
Metrics.SampledNumY,
|
|
Metrics.SampledNumZ,
|
|
static_cast<long long>(Metrics.NumSampled));
|
|
}
|
|
|
|
/** Configure the exact fitted XY window used by the player-fit law. */
|
|
inline void ConfigureMouthWindow(
|
|
FVoxelStrateMeasureSettings& InOutSettings,
|
|
const FVector& ArrivalPoint,
|
|
const FVector& DeparturePoint,
|
|
ECaveGeneratorType Archetype)
|
|
{
|
|
InOutSettings.CenterXY = FVector2D(
|
|
0.5f * (ArrivalPoint.X + DeparturePoint.X),
|
|
0.5f * (ArrivalPoint.Y + DeparturePoint.Y));
|
|
InOutSettings.CoverPointA = FVector2D(ArrivalPoint.X, ArrivalPoint.Y);
|
|
InOutSettings.CoverPointB = FVector2D(DeparturePoint.X, DeparturePoint.Y);
|
|
InOutSettings.CoverMarginVoxels = FMath::CeilToFloat(
|
|
InOutSettings.PlayerCapsuleRadiusVoxels) + 2.0f;
|
|
InOutSettings.bIncludeOriginInCoverWindow = NeedsOriginInWindow(Archetype);
|
|
}
|
|
}
|
|
|
|
#endif // WITH_DEV_AUTOMATION_TESTS
|