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:
@@ -1119,3 +1119,74 @@ needs Jahni's go-ahead on scope (~40 sites, re-rolls every world, one build).
|
|||||||
- **`SurfaceWorld`** (`OPSTACK-DECOMPOSITION §5`): the biggest op-stack payoff, most care.
|
- **`SurfaceWorld`** (`OPSTACK-DECOMPOSITION §5`): the biggest op-stack payoff, most care.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-27 — C9 FIXED AT THE CAUSE: FPSemantics = Precise, IWYU debt cleared.
|
||||||
|
|
||||||
|
**Jahni's call, and the right one:** *"I believe we could put FPSemantics to precise? Clearing up
|
||||||
|
anything that makes us not use it, then skip on the cross-platform same result for now and continue
|
||||||
|
on the rework of the opstack."* Fix the cause rather than measure the symptom — if both toolchains
|
||||||
|
compile IEEE-754 with no contraction, they agree **by construction** and there is nothing left to
|
||||||
|
compare.
|
||||||
|
|
||||||
|
**Verified in the UE 5.7 source rather than assumed** (`E:\Program Files\Epic Games\UE_5.7`):
|
||||||
|
|
||||||
|
| | `Default` | `Precise` |
|
||||||
|
|---|---|---|
|
||||||
|
| `VCToolChain.cs:1264` (Windows/MSVC) | `/fp:fast` | **`/fp:precise`** |
|
||||||
|
| `ClangToolChain.cs:712` (Linux/Mac) | `-ffp-contract=off` | **`-ffp-contract=off`** |
|
||||||
|
|
||||||
|
So `Default` really did mean **opposite** float models per platform, and `Precise` collapses them
|
||||||
|
onto the same one. `FPSemantics` is a `ModuleRules` property (`ModuleRules.cs:777`), so per-module
|
||||||
|
is the right granularity.
|
||||||
|
|
||||||
|
### The IWYU debt, cleared
|
||||||
|
|
||||||
|
Losing the shared PCH is what the debt was hiding behind. All uses turned out to be pointers,
|
||||||
|
`TWeakObjectPtr` or `TSubclassOf` parameters, so **forward declarations suffice** — only the
|
||||||
|
templates and macros needed real includes:
|
||||||
|
|
||||||
|
| Header | Added |
|
||||||
|
|---|---|
|
||||||
|
| `VoxelBiomeDefinition.h` | `class UMaterialInterface;` |
|
||||||
|
| `VoxelSettings.h` | `class UMaterialInterface;` |
|
||||||
|
| `VoxelStrateDefinition.h` | `Templates/SubclassOf.h` + `UMaterialInterface`, `USoundBase`, `AActor` |
|
||||||
|
| `VoxelStrateTypes.h` | `Templates/SubclassOf.h` + `AActor` |
|
||||||
|
| `VoxelContentManager.h` | `Templates/SubclassOf.h` + `AActor` |
|
||||||
|
| `VoxelAtmosphereManager.h` | `class AActor;` |
|
||||||
|
| `VoxelDensityVolume.h` | **`DrawDebugHelpers.h`** + `class AActor;` |
|
||||||
|
|
||||||
|
**`VoxelDensityVolume.h` was the one worth catching.** It uses `ENABLE_DRAW_DEBUG` in an `#if`, and
|
||||||
|
an **undefined macro in an `#if` is silently 0** — so without the include the debug block would have
|
||||||
|
vanished without a single warning, rather than failing the build. Include paths verified against the
|
||||||
|
engine tree (`Engine/Public/DrawDebugHelpers.h`, `CoreUObject/Public/Templates/SubclassOf.h`), not
|
||||||
|
guessed.
|
||||||
|
|
||||||
|
**Expect a residual tail.** The shared PCH hid these for years and only a real build enumerates them
|
||||||
|
all. Any further "undefined type" error from this build is IWYU, **not** the float setting — the fix
|
||||||
|
is to add the include, never to revert `FPSemantics`. That instruction is now written into
|
||||||
|
`VoxelForge.Build.cs` where the next person will hit it.
|
||||||
|
|
||||||
|
### Also landed: the cross-platform instrument (kept, not blocking)
|
||||||
|
|
||||||
|
`VoxelForge.Determinism.CrossPlatformDigest` — two FNV-1a digests over a fixed integer grid (no RNG,
|
||||||
|
so the sample set cannot itself diverge):
|
||||||
|
- **SHAPE** — the sign of density only. This is all the mesher reads, so it *is* the world: same
|
||||||
|
shape digest ⇒ same cavities, same walls, same collision. Jahni's "99.99% reproducible", literally.
|
||||||
|
- **FIELD** — every float bit. Differing while SHAPE matches ⇒ sub-voxel vertex wobble, harmless.
|
||||||
|
|
||||||
|
Plus `NearIso`: how many samples sit within 1e-4 of the isosurface, i.e. how many could *possibly*
|
||||||
|
flip sign under a float-model change. That **bounds** the risk instead of assuming it. Digests are
|
||||||
|
reported, not asserted, until pinned — pinning before the platforms agree would just carve the
|
||||||
|
divergence into the test. Per Jahni, the cross-platform comparison is deferred; the test costs
|
||||||
|
nothing to leave in and becomes a permanent regression guard the day someone runs it on Linux.
|
||||||
|
|
||||||
|
**UNVERIFIED:** none of this is compiled. **Expect a perf regression** — `/fp:precise` forbids the
|
||||||
|
reassociation and FMA contraction `/fp:fast` allowed, on a noise-heavy hot path. Worth measuring
|
||||||
|
against `ARCHITECTURE §8.10` rather than assuming it is small.
|
||||||
|
|
||||||
|
**Next single action:** build. Then, per Jahni, back to the opstack — `SurfaceWorld`
|
||||||
|
(`OPSTACK-DECOMPOSITION §5`), the biggest payoff and the most care: the T1.a column cache and the
|
||||||
|
exact-lattice `ClassifyTile` bound must both survive the port. `§C1` (bounded seed offsets) stays
|
||||||
|
open and still wants doing before too many more archetypes copy it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -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 "CoreMinimal.h"
|
||||||
#include "VoxelAtmosphereManager.generated.h"
|
#include "VoxelAtmosphereManager.generated.h"
|
||||||
|
|
||||||
|
class AActor; // IWYU : pointeur / TWeakObjectPtr seulement / pointer-only
|
||||||
class UVoxelStrateManager;
|
class UVoxelStrateManager;
|
||||||
class UVoxelStrateDefinition;
|
class UVoxelStrateDefinition;
|
||||||
class UVoxelBiomeDefinition;
|
class UVoxelBiomeDefinition;
|
||||||
|
|||||||
@@ -22,6 +22,11 @@
|
|||||||
#include "VoxelStrateTypes.h" // FStrateDecoration / FStrateAmbientActor
|
#include "VoxelStrateTypes.h" // FStrateDecoration / FStrateAmbientActor
|
||||||
#include "VoxelBiomeDefinition.generated.h"
|
#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.
|
* UVoxelBiomeDefinition — one biome's identity, placement, terrain modulation and content.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -45,9 +45,11 @@
|
|||||||
#include "VoxelTypes.h"
|
#include "VoxelTypes.h"
|
||||||
#include "VoxelStrateTypes.h" // FStrateDecoration (resolved per dominant biome)
|
#include "VoxelStrateTypes.h" // FStrateDecoration (resolved per dominant biome)
|
||||||
#include "VoxelBiomeTypes.h" // FBiomeContext (per-column biome resolve on the worker)
|
#include "VoxelBiomeTypes.h" // FBiomeContext (per-column biome resolve on the worker)
|
||||||
|
#include "Templates/SubclassOf.h" // IWYU : TSubclassOf<AActor> (FRegionActorBucket & co)
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include "VoxelContentManager.generated.h"
|
#include "VoxelContentManager.generated.h"
|
||||||
|
|
||||||
|
class AActor; // IWYU : pointeur / TWeakObjectPtr / TSubclassOf seulement
|
||||||
class UVoxelStrateManager;
|
class UVoxelStrateManager;
|
||||||
class UVoxelStrateDefinition;
|
class UVoxelStrateDefinition;
|
||||||
class UVoxelGenerator;
|
class UVoxelGenerator;
|
||||||
|
|||||||
@@ -37,9 +37,17 @@
|
|||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Containers/Queue.h"
|
#include "Containers/Queue.h"
|
||||||
#include "VoxelTypes.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 <atomic>
|
||||||
#include "VoxelDensityVolume.generated.h"
|
#include "VoxelDensityVolume.generated.h"
|
||||||
|
|
||||||
|
class AActor; // IWYU : pointeur / TWeakObjectPtr seulement / pointer-only
|
||||||
class UVoxelGenerator;
|
class UVoxelGenerator;
|
||||||
class UVoxelSettings;
|
class UVoxelSettings;
|
||||||
class UVolumeTexture;
|
class UVolumeTexture;
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
#include "VoxelStrateDefinition.h"
|
#include "VoxelStrateDefinition.h"
|
||||||
#include "VoxelSettings.generated.h"
|
#include "VoxelSettings.generated.h"
|
||||||
|
|
||||||
|
// IWYU : pointeur seulement (VoxelMaterial). / Pointer-only use.
|
||||||
|
class UMaterialInterface;
|
||||||
|
|
||||||
UCLASS(BlueprintType)
|
UCLASS(BlueprintType)
|
||||||
class UVoxelSettings : public UPrimaryDataAsset
|
class UVoxelSettings : public UPrimaryDataAsset
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,9 +18,16 @@
|
|||||||
#include "GameplayTagContainer.h"
|
#include "GameplayTagContainer.h"
|
||||||
#include "VoxelStrateTypes.h"
|
#include "VoxelStrateTypes.h"
|
||||||
#include "VoxelBiomeTypes.h"
|
#include "VoxelBiomeTypes.h"
|
||||||
|
#include "Templates/SubclassOf.h" // IWYU : TSubclassOf<AActor> (Atmosphere/Ceiling/FloorLayerActor)
|
||||||
#include "VoxelStrateDefinition.generated.h"
|
#include "VoxelStrateDefinition.generated.h"
|
||||||
|
|
||||||
class UVoxelBiomeDefinition;
|
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.
|
* UVoxelStrateDefinition — The content bag for a strate type.
|
||||||
|
|||||||
@@ -14,9 +14,11 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "GameplayTagContainer.h"
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "Templates/SubclassOf.h" // IWYU : TSubclassOf<AActor> (FPlacementProfile & co)
|
||||||
#include "VoxelStrateTypes.generated.h"
|
#include "VoxelStrateTypes.generated.h"
|
||||||
|
|
||||||
class UVoxelBiomeDefinition; // FPlacementProfile::RequiredBiome (optional per-entry biome filter)
|
class UVoxelBiomeDefinition; // FPlacementProfile::RequiredBiome (optional per-entry biome filter)
|
||||||
|
class AActor; // IWYU : paramètre de TSubclassOf seulement / TSubclassOf param only
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// ENUMS
|
// ENUMS
|
||||||
|
|||||||
@@ -12,25 +12,51 @@ public class VoxelForge : ModuleRules
|
|||||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
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
|
// Jahni, 2026-07-27: the game must be playable on both Linux and Windows, either side
|
||||||
// environment) makes VoxelForge ineligible for the ENGINE'S SHARED PCH: UBT can only
|
// hosting. The MP design replicates the SEED and has every peer regenerate the terrain, so
|
||||||
// share a precompiled header between modules whose compile environments match. The
|
// a Windows host and a Linux client must agree on the density field.
|
||||||
// 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.
|
|
||||||
//
|
//
|
||||||
// So the plugin has a latent IWYU (include-what-you-use) debt: several public headers
|
// They did not, by construction. UBT resolves FPSemanticsMode.Default differently per
|
||||||
// use engine types they never include. That is worth fixing on its own terms one day
|
// toolchain (verified in UE 5.7 source, not assumed):
|
||||||
// (UE has been moving away from implicit shared-PCH includes for years), but it is a
|
// VCToolChain.cs:1264 Default/Imprecise -> "/fp:fast" (Windows/MSVC)
|
||||||
// real chunk of work and must not be attempted inside an unrelated diagnostic.
|
// 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
|
// Precise resolves to "/fp:precise" on MSVC and "-ffp-contract=off" on Clang — both
|
||||||
// differently per translation unit under /fp:fast — is now answered inside
|
// IEEE-754 compliant with no FMA contraction, so the two toolchains agree BY CONSTRUCTION
|
||||||
// VoxelForge.OpStack.MazeEquivalence instead, by compiling a verbatim copy of the
|
// rather than by luck. That is the fix for AUDIT-2026-07.md C9.
|
||||||
// Maze core into the TEST's translation unit and comparing all three. No build
|
//
|
||||||
// settings involved, and it cannot break anything.
|
// 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:
|
// Modules we depend on:
|
||||||
// - Core: Basic types (TArray, FString, etc.)
|
// - Core: Basic types (TArray, FString, etc.)
|
||||||
|
|||||||
Reference in New Issue
Block a user