build: FPSemantics = Precise + clear the IWYU debt that blocked it
Jahni: cross-platform play (Linux and Windows, either side hosting) is a product requirement, so fix C9 at the cause instead of measuring the symptom. Verified in UE 5.7 source rather than assumed: VCToolChain.cs:1264 Default -> /fp:fast Precise -> /fp:precise ClangToolChain.cs:712 Default -> -ffp-contract=off Precise -> -ffp-contract=off Default really did mean opposite float models per platform; Precise collapses them onto the same one, so a Windows host and a Linux client agree by construction. Losing the shared PCH is what the IWYU debt hid behind. Every use turned out to be a pointer, TWeakObjectPtr or TSubclassOf parameter, so forward declarations suffice; only the templates and macros needed real includes. Seven headers fixed. VoxelDensityVolume.h was the one worth catching: it tests ENABLE_DRAW_DEBUG in an #if, and an undefined macro there is silently 0 — the debug block would have vanished without a warning rather than failing the build. Include paths verified against the engine tree, not guessed. Expect a residual tail; the shared PCH hid these for years and only a build enumerates them all. Build.cs now says so, and says the fix is to add the include rather than revert FPSemantics. Also adds VoxelForge.Determinism.CrossPlatformDigest: SHAPE digest (sign of density = the world) and FIELD digest (bit-for-bit) over a fixed integer grid, plus NearIso to bound how many samples could flip sign at all. Reports rather than asserts until pinned. The cross-platform comparison itself is deferred per Jahni. Expect a perf regression from losing reassociation and contraction on a noise-heavy hot path — measure against ARCHITECTURE 8.10. UNVERIFIED: not compiled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
// VoxelForgeCrossPlatformTest.cpp
|
||||
// LA QUESTION MULTIJOUEUR, RENDUE MESURABLE / THE MULTIPLAYER QUESTION, MADE MEASURABLE
|
||||
//
|
||||
// Jahni, 2026-07-27 : *« je voudrais que le jeu soit jouable sur les deux plateformes, Linux et
|
||||
// Windows, donc un hôte Windows avec un client Linux pourrait arriver, et l'inverse. »*
|
||||
// Et la barre d'acceptation : *« 99.99% au pire reproductible si deux personnes partagent la même
|
||||
// seed, puisque tout le monde le reconstruit en multijoueur. »*
|
||||
//
|
||||
// ⚠️ LE PROBLÈME (AUDIT §C9) : le MÊME `FPSemanticsMode.Default` d'UBT ne veut pas dire la même
|
||||
// chose selon la toolchain — `VCToolChain` (Windows/MSVC) le résout en **`/fp:fast`**,
|
||||
// `ClangToolChain` (Linux/Mac/Windows-Clang) le résout en **précis + `-ffp-contract=off`**. Deux
|
||||
// builds de la MÊME source sont donc compilés sous des règles flottantes OPPOSÉES. Un hôte Windows
|
||||
// et un client Linux ne sont pas seulement *autorisés* à diverger : ils sont compilés pour.
|
||||
//
|
||||
// ─────────────────────────────────────────────────────────────────────────────────────────
|
||||
// CE TEST NE CORRIGE RIEN — IL MESURE, et c'est ce qui manque
|
||||
// ─────────────────────────────────────────────────────────────────────────────────────────
|
||||
// Aucune quantité de raisonnement ne dit à quel point les deux plateformes divergent : il faut le
|
||||
// LIRE. Ce test produit deux empreintes du même monde, à la même seed, et les affiche. On le lance
|
||||
// sur Windows, on le lance sur Linux, on compare les deux lignes.
|
||||
//
|
||||
// • **EMPREINTE DE FORME** — le SIGNE de la densité seulement (solide / air). C'est la SEULE
|
||||
// chose que le mesher lit (`D >= IsoLevel ⇒ air`). Si cette empreinte est identique, les deux
|
||||
// plateformes ont **le même monde** : mêmes cavités, mêmes murs, même navigabilité, même
|
||||
// collision au voxel près. C'est littéralement le critère « 99.99% reproductible » de Jahni.
|
||||
//
|
||||
// • **EMPREINTE DE CHAMP** — tous les bits de tous les floats. Identique ⇒ reproductibilité
|
||||
// BIT à BIT. Différente alors que la forme est identique ⇒ la divergence est un frémissement
|
||||
// sous-voxel de la position des sommets, sans conséquence de jeu.
|
||||
//
|
||||
// C'est le bon découpage parce qu'il sépare les deux échecs possibles, qui n'ont pas du tout la
|
||||
// même gravité :
|
||||
//
|
||||
// forme == && champ == ⇒ parfait, rien à faire.
|
||||
// forme == && champ != ⇒ ACCEPTABLE. Les sommets bougent de ~1e-5 voxel. Personne ne le voit,
|
||||
// rien ne s'y accroche — SAUF si un jour on compare des hashs de
|
||||
// géométrie entre pairs. À ne pas faire, donc.
|
||||
// forme != ⇒ **INACCEPTABLE**. Un voxel solide chez l'un est de l'air chez
|
||||
// l'autre : un joueur traverse un mur que l'autre voit plein.
|
||||
//
|
||||
// ⚠️ POURQUOI LA FORME A DE BONNES CHANCES DE TENIR MÊME AUJOURD'HUI — et pourquoi il faut quand
|
||||
// même la mesurer : toutes les décisions STRUCTURELLES du plugin (quelle arête de treillis est
|
||||
// ouverte, quelle cellule porte une colonne, où sont les salles et les passages) passent par
|
||||
// `VoxelHash::*`, c.-à-d. de l'ARITHMÉTIQUE ENTIÈRE, identique sur toute plateforme. Le flottant
|
||||
// ne décide que la POSITION de la surface. Un signe ne bascule donc que si un échantillon tombe à
|
||||
// ~1e-5 de l'isosurface — d'où le troisième chiffre affiché, `NearIso`, qui BORNE le risque au
|
||||
// lieu de le supposer.
|
||||
//
|
||||
// The structural decisions all go through integer hashing, so only the surface POSITION is
|
||||
// float-decided. A sign flips only where a sample sits within ~1e-5 of the isosurface, which is why
|
||||
// NearIso is reported: it bounds the risk instead of assuming it.
|
||||
//
|
||||
// ─────────────────────────────────────────────────────────────────────────────────────────
|
||||
// COMMENT S'EN SERVIR / HOW TO USE THIS
|
||||
// ─────────────────────────────────────────────────────────────────────────────────────────
|
||||
// 1. Lancer sur Windows, noter les deux empreintes.
|
||||
// 2. Lancer sur Linux, comparer.
|
||||
// 3. Une fois `FPSemantics = Precise` posé sur le module (AUDIT §C9, bloqué par la dette IWYU)
|
||||
// et les deux plateformes d'accord : **épingler** les valeurs dans `PinnedShapeDigest` /
|
||||
// `PinnedFieldDigest` ci-dessous. Le test devient alors un garde-fou permanent — toute
|
||||
// régression de déterminisme échoue bruyamment, sur la plateforme qui a dérivé.
|
||||
//
|
||||
// Tant que les constantes valent 0, le test ne peut pas échouer sur les empreintes : il RAPPORTE.
|
||||
// C'est délibéré — épingler une valeur avant que les plateformes soient d'accord ne ferait que
|
||||
// graver la divergence dans le test.
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "VoxelForgeTestFixture.h"
|
||||
#include "VoxelGenerator.h"
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FVoxelForgeCrossPlatformTest,
|
||||
"VoxelForge.Determinism.CrossPlatformDigest",
|
||||
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
namespace
|
||||
{
|
||||
//=========================================================================
|
||||
// ÉPINGLES / PINS — 0 = « pas encore d'accord de référence », le test rapporte sans juger.
|
||||
//=========================================================================
|
||||
// À remplir UNIQUEMENT quand Windows et Linux rendent la même valeur. Voir l'en-tête.
|
||||
constexpr uint64 PinnedShapeDigest = 0;
|
||||
constexpr uint64 PinnedFieldDigest = 0;
|
||||
|
||||
// FNV-1a 64 bits, octet par octet, **entier pur**. Volontairement écrit à la main plutôt que
|
||||
// pris dans le moteur : une empreinte de déterminisme ne doit dépendre d'aucune implémentation
|
||||
// qui pourrait, elle, varier. Ici il n'y a que des `^` et des `*` sur uint64.
|
||||
// Hand-rolled on purpose: a determinism digest must not depend on an implementation that could
|
||||
// itself vary. Nothing here but XOR and multiply on uint64.
|
||||
constexpr uint64 FnvOffsetBasis = 0xcbf29ce484222325ull;
|
||||
constexpr uint64 FnvPrime = 0x00000100000001b3ull;
|
||||
|
||||
FORCEINLINE void FnvAccumByte(uint64& H, uint8 B)
|
||||
{
|
||||
H ^= (uint64)B;
|
||||
H *= FnvPrime;
|
||||
}
|
||||
|
||||
FORCEINLINE void FnvAccumU32(uint64& H, uint32 V)
|
||||
{
|
||||
FnvAccumByte(H, (uint8)( V & 0xFFu));
|
||||
FnvAccumByte(H, (uint8)((V >> 8) & 0xFFu));
|
||||
FnvAccumByte(H, (uint8)((V >> 16) & 0xFFu));
|
||||
FnvAccumByte(H, (uint8)((V >> 24) & 0xFFu));
|
||||
}
|
||||
|
||||
/** Bits d'un float, avec les NaN NORMALISÉS : un NaN a plusieurs représentations et rien ne
|
||||
* garantit que deux plateformes produisent la même. On les compte à part. */
|
||||
FORCEINLINE uint32 FloatBitsNormalised(float V, bool& bOutWasNaN)
|
||||
{
|
||||
bOutWasNaN = FMath::IsNaN(V);
|
||||
if (bOutWasNaN) { return 0x7FC00000u; }
|
||||
return *reinterpret_cast<const uint32*>(&V);
|
||||
}
|
||||
}
|
||||
|
||||
bool FVoxelForgeCrossPlatformTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
using namespace VoxelForgeTest;
|
||||
|
||||
FTestWorld World;
|
||||
World.Build();
|
||||
if (!World.IsValid())
|
||||
{
|
||||
AddError(World.WhyInvalid());
|
||||
return false;
|
||||
}
|
||||
|
||||
const UVoxelGenerator* Gen = World.Generator.Get();
|
||||
|
||||
//=========================================================================
|
||||
// LA GRILLE — entièrement déterministe, SANS RNG
|
||||
//=========================================================================
|
||||
// Pas de `FRandomStream` ici, contrairement aux autres tests : l'ensemble des points doit être
|
||||
// identique sur les deux plateformes SANS dépendre d'une seule ligne de code partagé. Une
|
||||
// boucle entière sur des bornes entières ne peut pas diverger.
|
||||
// No RNG: the point set must be identical across platforms without depending on any shared
|
||||
// code at all. An integer loop over integer bounds cannot diverge.
|
||||
const int32 TopVoxelZ = World.TopChunkZ * CHUNK_SIZE + CHUNK_SIZE - 1;
|
||||
const int32 BottomVoxelZ = World.BottomChunkZ * CHUNK_SIZE;
|
||||
|
||||
constexpr int32 XYStep = 8;
|
||||
constexpr int32 ZStep = 8;
|
||||
const int32 XYExtent = 3 * CHUNK_SIZE; // couvre la spine (0,0), les passages et le rocher
|
||||
|
||||
uint64 ShapeDigest = FnvOffsetBasis;
|
||||
uint64 FieldDigest = FnvOffsetBasis;
|
||||
|
||||
int32 NumSamples = 0, NumSolid = 0, NumNaN = 0, NumNearIso = 0;
|
||||
|
||||
// `NearIso` : à quelle distance de zéro un échantillon doit-il être pour qu'une différence
|
||||
// d'ULP puisse faire basculer son SIGNE ? Les écarts mesurés entre le `switch` et la pile
|
||||
// valent ~1e-5 au pire (AUDIT §C10) ; un écart entre modèles flottants est du même ordre.
|
||||
// Tout échantillon plus loin que ça de l'isosurface ne peut PAS changer de côté.
|
||||
constexpr float NearIsoBand = 1.0e-4f; // 10× la pire divergence observée : marge délibérée
|
||||
|
||||
for (int32 Z = BottomVoxelZ; Z <= TopVoxelZ; Z += ZStep)
|
||||
{
|
||||
for (int32 Y = -XYExtent; Y <= XYExtent; Y += XYStep)
|
||||
{
|
||||
for (int32 X = -XYExtent; X <= XYExtent; X += XYStep)
|
||||
{
|
||||
const float D = Gen->GetDensityAt((float)X, (float)Y, (float)Z);
|
||||
|
||||
bool bWasNaN = false;
|
||||
const uint32 Bits = FloatBitsNormalised(D, bWasNaN);
|
||||
if (bWasNaN) { ++NumNaN; }
|
||||
|
||||
// FORME : un seul bit par échantillon — le côté de l'isosurface, ce que lit le
|
||||
// mesher. C'est l'empreinte qui doit tenir entre plateformes.
|
||||
const bool bAir = (D >= 0.0f);
|
||||
if (!bAir) { ++NumSolid; }
|
||||
FnvAccumByte(ShapeDigest, bAir ? 1u : 0u);
|
||||
|
||||
// CHAMP : tous les bits.
|
||||
FnvAccumU32(FieldDigest, Bits);
|
||||
|
||||
if (!bWasNaN && FMath::Abs(D) < NearIsoBand) { ++NumNearIso; }
|
||||
++NumSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// LE RAPPORT — c'est le produit de ce test
|
||||
//=========================================================================
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("CROSS-PLATFORM DIGEST (seed %d, %d samples, step %d/%d)\n")
|
||||
TEXT(" SHAPE digest : 0x%016llX <- must match across Windows/Linux. This is the world.\n")
|
||||
TEXT(" FIELD digest : 0x%016llX <- bit-for-bit. May differ; see NearIso below.\n")
|
||||
TEXT(" solid %d / air %d / NaN %d"),
|
||||
World.Settings->Seed, NumSamples, XYStep, ZStep,
|
||||
ShapeDigest, FieldDigest, NumSolid, NumSamples - NumSolid, NumNaN));
|
||||
|
||||
// LE chiffre qui borne le risque, plutôt que de le supposer.
|
||||
if (NumNearIso == 0)
|
||||
{
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("NearIso: 0 of %d samples sit within %.1e of the isosurface. No sample is close ")
|
||||
TEXT("enough for a float-model difference (~1e-5 worst observed) to flip its SIGN, so ")
|
||||
TEXT("the SHAPE digest is robust to the /fp:fast-vs-precise split by a 10x margin at ")
|
||||
TEXT("these sample points. That is evidence, not proof -- it covers this grid, not ")
|
||||
TEXT("every voxel in a world."),
|
||||
NumSamples, NearIsoBand));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddWarning(FString::Printf(
|
||||
TEXT("NearIso: %d of %d samples sit within %.1e of the isosurface -- close enough that ")
|
||||
TEXT("a float-model difference could flip their SIGN, which is a solid-vs-air ")
|
||||
TEXT("disagreement between a Windows host and a Linux client. This is the concrete ")
|
||||
TEXT("mechanism behind AUDIT C9, and the count is roughly how many voxels per %d are at ")
|
||||
TEXT("risk. It does not mean they DO differ -- run this on both platforms and compare ")
|
||||
TEXT("the SHAPE digest to find out."),
|
||||
NumNearIso, NumSamples, NearIsoBand, NumSamples));
|
||||
}
|
||||
|
||||
TestEqual(TEXT("no sample produced NaN"), NumNaN, 0);
|
||||
|
||||
// Un monde entièrement solide ou entièrement vide rendrait les empreintes vraies mais vides de
|
||||
// sens. Garde-fou minimal contre un test qui se félicite de ne rien mesurer.
|
||||
TestTrue(TEXT("the sampled world contains both solid and air (the digest is meaningful)"),
|
||||
NumSolid > 0 && NumSolid < NumSamples);
|
||||
|
||||
//=========================================================================
|
||||
// LES ÉPINGLES — inertes tant que personne ne les a posées
|
||||
//=========================================================================
|
||||
if (PinnedShapeDigest != 0)
|
||||
{
|
||||
TestEqual(TEXT("SHAPE digest matches the pinned cross-platform reference"),
|
||||
ShapeDigest, PinnedShapeDigest);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddInfo(TEXT("SHAPE digest is not pinned yet. Pin it only once Windows and Linux agree -- ")
|
||||
TEXT("pinning first would just carve the divergence into the test."));
|
||||
}
|
||||
|
||||
if (PinnedFieldDigest != 0)
|
||||
{
|
||||
TestEqual(TEXT("FIELD digest matches the pinned cross-platform reference"),
|
||||
FieldDigest, PinnedFieldDigest);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // WITH_DEV_AUTOMATION_TESTS
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "VoxelAtmosphereManager.generated.h"
|
||||
|
||||
class AActor; // IWYU : pointeur / TWeakObjectPtr seulement / pointer-only
|
||||
class UVoxelStrateManager;
|
||||
class UVoxelStrateDefinition;
|
||||
class UVoxelBiomeDefinition;
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
#include "VoxelStrateTypes.h" // FStrateDecoration / FStrateAmbientActor
|
||||
#include "VoxelBiomeDefinition.generated.h"
|
||||
|
||||
// IWYU : utilisé en pointeur seulement ⇒ déclaration avant. Fournie gratuitement par le PCH
|
||||
// partagé jusqu'ici ; `FPSemantics = Precise` nous en sort. / Pointer-only use, so a forward
|
||||
// declaration is enough. The shared PCH used to provide this for free.
|
||||
class UMaterialInterface;
|
||||
|
||||
/**
|
||||
* UVoxelBiomeDefinition — one biome's identity, placement, terrain modulation and content.
|
||||
*/
|
||||
|
||||
@@ -45,9 +45,11 @@
|
||||
#include "VoxelTypes.h"
|
||||
#include "VoxelStrateTypes.h" // FStrateDecoration (resolved per dominant biome)
|
||||
#include "VoxelBiomeTypes.h" // FBiomeContext (per-column biome resolve on the worker)
|
||||
#include "Templates/SubclassOf.h" // IWYU : TSubclassOf<AActor> (FRegionActorBucket & co)
|
||||
#include <atomic>
|
||||
#include "VoxelContentManager.generated.h"
|
||||
|
||||
class AActor; // IWYU : pointeur / TWeakObjectPtr / TSubclassOf seulement
|
||||
class UVoxelStrateManager;
|
||||
class UVoxelStrateDefinition;
|
||||
class UVoxelGenerator;
|
||||
|
||||
@@ -37,9 +37,17 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "Containers/Queue.h"
|
||||
#include "VoxelTypes.h"
|
||||
// ⚠️ IWYU, ET CELUI-CI EST PIÉGEUX : `ENABLE_DRAW_DEBUG` est utilisé en `#if` plus bas. Un macro
|
||||
// NON DÉFINI vaut 0 dans un `#if` — donc sans cet include le bloc de debug disparaît EN SILENCE au
|
||||
// lieu de provoquer une erreur de compilation. Il venait du PCH partagé ; `FPSemantics = Precise`
|
||||
// (AUDIT §C9) nous en prive. Défini par DrawDebugHelpers.h (vérifié dans UE 5.7).
|
||||
// An UNDEFINED macro evaluates to 0 in an #if, so without this include the debug block vanishes
|
||||
// SILENTLY instead of failing the build. Defined by DrawDebugHelpers.h (verified in UE 5.7).
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include <atomic>
|
||||
#include "VoxelDensityVolume.generated.h"
|
||||
|
||||
class AActor; // IWYU : pointeur / TWeakObjectPtr seulement / pointer-only
|
||||
class UVoxelGenerator;
|
||||
class UVoxelSettings;
|
||||
class UVolumeTexture;
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#include "VoxelStrateDefinition.h"
|
||||
#include "VoxelSettings.generated.h"
|
||||
|
||||
// IWYU : pointeur seulement (VoxelMaterial). / Pointer-only use.
|
||||
class UMaterialInterface;
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class UVoxelSettings : public UPrimaryDataAsset
|
||||
{
|
||||
|
||||
@@ -18,9 +18,16 @@
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "VoxelStrateTypes.h"
|
||||
#include "VoxelBiomeTypes.h"
|
||||
#include "Templates/SubclassOf.h" // IWYU : TSubclassOf<AActor> (Atmosphere/Ceiling/FloorLayerActor)
|
||||
#include "VoxelStrateDefinition.generated.h"
|
||||
|
||||
class UVoxelBiomeDefinition;
|
||||
// IWYU : tous en pointeur ou en paramètre de TSubclassOf ⇒ déclarations avant suffisantes.
|
||||
// Le PCH partagé les fournissait ; `FPSemantics = Precise` (AUDIT §C9) nous en prive.
|
||||
// All pointer-only or TSubclassOf parameters, so forward declarations suffice.
|
||||
class UMaterialInterface;
|
||||
class USoundBase;
|
||||
class AActor;
|
||||
|
||||
/**
|
||||
* UVoxelStrateDefinition — The content bag for a strate type.
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "Templates/SubclassOf.h" // IWYU : TSubclassOf<AActor> (FPlacementProfile & co)
|
||||
#include "VoxelStrateTypes.generated.h"
|
||||
|
||||
class UVoxelBiomeDefinition; // FPlacementProfile::RequiredBiome (optional per-entry biome filter)
|
||||
class AActor; // IWYU : paramètre de TSubclassOf seulement / TSubclassOf param only
|
||||
|
||||
//=============================================================================
|
||||
// ENUMS
|
||||
|
||||
@@ -12,25 +12,51 @@ public class VoxelForge : ModuleRules
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
// ============================================================================
|
||||
// ⚠️ DO NOT SET `FPSemantics` HERE — tried 2026-07-27, it does not build.
|
||||
// FLOAT MODEL — pinned to Precise so Windows and Linux compute the SAME WORLD.
|
||||
// ============================================================================
|
||||
// Setting FPSemantics (or any other property that alters this module's compile
|
||||
// environment) makes VoxelForge ineligible for the ENGINE'S SHARED PCH: UBT can only
|
||||
// share a precompiled header between modules whose compile environments match. The
|
||||
// build then fails with ~30 "undefined type" errors — UMaterialInterface, USoundBase,
|
||||
// TSubclassOf<AActor>, APawn, ENABLE_DRAW_DEBUG — none of which are FP-related. They
|
||||
// are includes this plugin has always relied on the shared PCH to provide for free.
|
||||
// Jahni, 2026-07-27: the game must be playable on both Linux and Windows, either side
|
||||
// hosting. The MP design replicates the SEED and has every peer regenerate the terrain, so
|
||||
// a Windows host and a Linux client must agree on the density field.
|
||||
//
|
||||
// So the plugin has a latent IWYU (include-what-you-use) debt: several public headers
|
||||
// use engine types they never include. That is worth fixing on its own terms one day
|
||||
// (UE has been moving away from implicit shared-PCH includes for years), but it is a
|
||||
// real chunk of work and must not be attempted inside an unrelated diagnostic.
|
||||
// They did not, by construction. UBT resolves FPSemanticsMode.Default differently per
|
||||
// toolchain (verified in UE 5.7 source, not assumed):
|
||||
// VCToolChain.cs:1264 Default/Imprecise -> "/fp:fast" (Windows/MSVC)
|
||||
// ClangToolChain.cs:712 Default/Precise -> "-ffp-contract=off" (Linux/Mac/Clang)
|
||||
// So the same source was compiled under OPPOSITE float rules depending on who built it.
|
||||
//
|
||||
// The FP question it was meant to settle — whether identical source reassociates
|
||||
// differently per translation unit under /fp:fast — is now answered inside
|
||||
// VoxelForge.OpStack.MazeEquivalence instead, by compiling a verbatim copy of the
|
||||
// Maze core into the TEST's translation unit and comparing all three. No build
|
||||
// settings involved, and it cannot break anything.
|
||||
// Precise resolves to "/fp:precise" on MSVC and "-ffp-contract=off" on Clang — both
|
||||
// IEEE-754 compliant with no FMA contraction, so the two toolchains agree BY CONSTRUCTION
|
||||
// rather than by luck. That is the fix for AUDIT-2026-07.md C9.
|
||||
//
|
||||
// COST: /fp:precise forbids the reassociation and contraction /fp:fast allowed, on a
|
||||
// noise-heavy hot path. Expect a measurable perf regression and check it against
|
||||
// ARCHITECTURE 8.10 — determinism across platforms is worth paying for, but the price
|
||||
// should be known, not assumed.
|
||||
//
|
||||
// VERIFY: run VoxelForge.Determinism.CrossPlatformDigest on both platforms and compare the
|
||||
// SHAPE digest (sign of density = the world) and the FIELD digest (bit-for-bit). Pin the
|
||||
// values in that test once they agree, and it guards this forever after.
|
||||
FPSemantics = FPSemanticsMode.Precise;
|
||||
|
||||
// ============================================================================
|
||||
// ⚠️ HISTORY — why this took a second attempt (kept: it explains the includes below)
|
||||
// ============================================================================
|
||||
// Setting FPSemantics (or any property that alters this module's compile environment)
|
||||
// makes VoxelForge ineligible for the ENGINE'S SHARED PCH — UBT can only share a
|
||||
// precompiled header between modules whose compile environments match. The first attempt
|
||||
// (2026-07-27) was therefore reverted: it failed with ~30 "undefined type" errors that
|
||||
// were not FP-related at all — UMaterialInterface, USoundBase, TSubclassOf<AActor>,
|
||||
// ENABLE_DRAW_DEBUG — i.e. includes this plugin had always taken from the shared PCH for
|
||||
// free. That is a latent IWYU debt, not an FP problem.
|
||||
//
|
||||
// ⚠️ SO IF YOU SEE "undefined type" ERRORS HERE, THEY ARE IWYU, NOT FLOAT SETTINGS.
|
||||
// The fix is to add the missing include or forward declaration to the header that needs
|
||||
// it — never to revert FPSemantics, which is now load-bearing for cross-platform play.
|
||||
// Headers fixed on 2026-07-27: VoxelBiomeDefinition, VoxelSettings, VoxelStrateDefinition,
|
||||
// VoxelStrateTypes, VoxelContentManager, VoxelAtmosphereManager, VoxelDensityVolume.
|
||||
// Expect a residual tail: the shared PCH hid these for years and only a build enumerates
|
||||
// them all. VoxelDensityVolume's was the nasty one — ENABLE_DRAW_DEBUG is used in an #if,
|
||||
// and an undefined macro there is silently 0 rather than an error.
|
||||
|
||||
// Modules we depend on:
|
||||
// - Core: Basic types (TArray, FString, etc.)
|
||||
|
||||
Reference in New Issue
Block a user