fix(streaming): collision-ready gate -- the pawn waits for a COOKED floor, not a submitted one
WORK-NEXT item 1, plus all three of item 6. Item 2 (the fused evaluator) untouched as
instructed.
⭐ RMC DOES EXPOSE WHAT WE NEEDED, so the gate rests on a real signal:
OnCollisionBodyUpdated (delegate -- diagnostic)
SetCollisionConfig() -> TFuture<ERealtimeMeshCollisionUpdateResult> (AUTHORITATIVE)
RealtimeMeshComponent was READ ONLY, per the owner's grant. Nothing there was modified.
THE INVARIANT
Until now nothing guaranteed a tile the player occupies has collision. apply_s ended at
mesh SUBMISSION; RMC cooks asynchronously and we recorded no completion, so no
measurement this project has ever taken said when physics could actually collide.
- per-tile CollisionReadyTiles state, with stale-callback protection
- HandleTileCollisionCookComplete driven by RMC's cook future
- pawn gated at BeginPlay and pre-physics on the FEET-SUPPORT tile -- deliberately not
the actor-centre tile, because the floor that catches a falling pawn is what must be
cooked -- plus the predicted movement path and solid/air traversal
- movement-component state saved/restored, so a temporary stream wait cannot
permanently change a character's movement mode
- new trace fields: mesh_submit_to_cook_s, collision_request_to_cook_s,
body_event_to_future_s -- three boundaries where we had one blurred number
ITEM 6, all three:
- LoadedTiles is now recorded only after SUCCESSFUL mesh submission, and a failed
mesh init retries. Previously a failed init left a tile marked loaded forever -- a
permanent hole.
- origin spawning fixed (the zero-position guard made a pawn at exactly
FVector::ZeroVector indistinguishable from "no pawn" -- and the owner spawns at 0,0)
- exact core-lattice validation for uniform whole-tile verdicts, which previously had
no safety net while block verdicts did
⚠️ I FIXED TWO COMPILE ERRORS MYSELF rather than spend another Codex round on them:
`GetMovementMode()` / `GetCustomMovementMode()` do not exist on
UCharacterMovementComponent -- MovementMode and CustomMovementMode are public fields.
Two lines. Codex did not build this round, so it could not have caught them.
VERIFIED BY ME: build 0 errors after the patch; export 1.022 / 1.055 s (aa62971 was
1.034 / 1.089); geometry SHA-256 b3e5f4c3..9b377 UNCHANGED -- generation is untouched,
as it should be for a streaming-lifecycle change.
⚠️ UNVERIFIED AND IMPORTANT: the submission-to-cook gap is still unmeasured -- Codex had
no live process to measure it in. The instrumentation exists and will report it on the
next play session. And the GATE ITSELF IS UNTESTED IN MOTION: if a support tile never
becomes collision-ready, the pawn stays frozen rather than falling. That trade is
deliberate, but the owner should watch for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
1 parent
58bdfb32de
commit
081cc5e459
5 files changed
+919
-69
No files matched your search
@@ -113,7 +113,7 @@ namespace
|
|||||||
if (Sample.bCacheHit) ++CacheHits;
|
if (Sample.bCacheHit) ++CacheHits;
|
||||||
if (Sample.bRegionHit) ++RegionHits;
|
if (Sample.bRegionHit) ++RegionHits;
|
||||||
Triangles += Sample.Triangles;
|
Triangles += Sample.Triangles;
|
||||||
Request.Add(Sample.RequestToReadySeconds);
|
Request.Add(Sample.RequestToApplySeconds);
|
||||||
Queue.Add(Sample.QueueWaitSeconds);
|
Queue.Add(Sample.QueueWaitSeconds);
|
||||||
Generation.Add(Sample.GenerationSeconds);
|
Generation.Add(Sample.GenerationSeconds);
|
||||||
Classify.Add(Sample.ClassifySeconds);
|
Classify.Add(Sample.ClassifySeconds);
|
||||||
@@ -123,7 +123,7 @@ namespace
|
|||||||
|
|
||||||
return FString::Printf(
|
return FString::Printf(
|
||||||
TEXT("{\"count\":%d,\"empty\":%d,\"all_air\":%d,\"all_solid\":%d,\"mixed\":%d,\"cache_hits\":%d,\"region_hits\":%d,\"triangles\":%lld,"
|
TEXT("{\"count\":%d,\"empty\":%d,\"all_air\":%d,\"all_solid\":%d,\"mixed\":%d,\"cache_hits\":%d,\"region_hits\":%d,\"triangles\":%lld,"
|
||||||
"\"request_ready_s\":{\"p50\":%s,\"p95\":%s,\"max\":%s},"
|
"\"request_apply_s\":{\"p50\":%s,\"p95\":%s,\"max\":%s},"
|
||||||
"\"queue_s\":{\"p50\":%s,\"p95\":%s,\"max\":%s},"
|
"\"queue_s\":{\"p50\":%s,\"p95\":%s,\"max\":%s},"
|
||||||
"\"generation_s\":{\"p50\":%s,\"p95\":%s,\"max\":%s},"
|
"\"generation_s\":{\"p50\":%s,\"p95\":%s,\"max\":%s},"
|
||||||
"\"classify_s\":{\"p50\":%s,\"p95\":%s,\"max\":%s},"
|
"\"classify_s\":{\"p50\":%s,\"p95\":%s,\"max\":%s},"
|
||||||
@@ -265,11 +265,11 @@ void RecordTile(const FTileSample& Sample)
|
|||||||
++GState->TileRecordsWritten;
|
++GState->TileRecordsWritten;
|
||||||
WriteEventLocked(*GState, TEXT("tile"), FString::Printf(
|
WriteEventLocked(*GState, TEXT("tile"), FString::Printf(
|
||||||
TEXT("\"level\":%d,\"verdict\":%d,\"empty\":%d,\"cache_hit\":%d,\"region_hit\":%d,\"triangles\":%d,"
|
TEXT("\"level\":%d,\"verdict\":%d,\"empty\":%d,\"cache_hit\":%d,\"region_hit\":%d,\"triangles\":%d,"
|
||||||
"\"request_to_ready_s\":%s,\"queue_wait_s\":%s,\"worker_queue_s\":%s,\"result_queue_s\":%s,"
|
"\"request_to_apply_s\":%s,\"queue_wait_s\":%s,\"worker_queue_s\":%s,\"result_queue_s\":%s,"
|
||||||
"\"generation_s\":%s,\"classify_s\":%s,\"mesh_s\":%s,\"stream_s\":%s,\"apply_s\":%s"),
|
"\"generation_s\":%s,\"classify_s\":%s,\"mesh_s\":%s,\"stream_s\":%s,\"apply_s\":%s"),
|
||||||
Sample.Level, Sample.Verdict, Sample.bEmpty ? 1 : 0,
|
Sample.Level, Sample.Verdict, Sample.bEmpty ? 1 : 0,
|
||||||
Sample.bCacheHit ? 1 : 0, Sample.bRegionHit ? 1 : 0, Sample.Triangles,
|
Sample.bCacheHit ? 1 : 0, Sample.bRegionHit ? 1 : 0, Sample.Triangles,
|
||||||
*Number(Sample.RequestToReadySeconds), *Number(Sample.QueueWaitSeconds),
|
*Number(Sample.RequestToApplySeconds), *Number(Sample.QueueWaitSeconds),
|
||||||
*Number(Sample.WorkerQueueSeconds), *Number(Sample.ResultQueueSeconds),
|
*Number(Sample.WorkerQueueSeconds), *Number(Sample.ResultQueueSeconds),
|
||||||
*Number(Sample.GenerationSeconds), *Number(Sample.ClassifySeconds),
|
*Number(Sample.GenerationSeconds), *Number(Sample.ClassifySeconds),
|
||||||
*Number(Sample.MeshSeconds), *Number(Sample.StreamSeconds), *Number(Sample.ApplySeconds)));
|
*Number(Sample.MeshSeconds), *Number(Sample.StreamSeconds), *Number(Sample.ApplySeconds)));
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -15,7 +15,9 @@ namespace VoxelForgeStartupTrace
|
|||||||
bool bCacheHit = false;
|
bool bCacheHit = false;
|
||||||
bool bRegionHit = false;
|
bool bRegionHit = false;
|
||||||
int32 Triangles = 0;
|
int32 Triangles = 0;
|
||||||
double RequestToReadySeconds = 0.0;
|
// This is worker-result -> game-thread mesh submission. Collision readiness is a later
|
||||||
|
// per-tile RMC completion event recorded separately as collision_ready.
|
||||||
|
double RequestToApplySeconds = 0.0;
|
||||||
double QueueWaitSeconds = 0.0;
|
double QueueWaitSeconds = 0.0;
|
||||||
double WorkerQueueSeconds = 0.0;
|
double WorkerQueueSeconds = 0.0;
|
||||||
double ResultQueueSeconds = 0.0;
|
double ResultQueueSeconds = 0.0;
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ constexpr float VOXEL_SIZE = 25.0f;
|
|||||||
// TRIVIAL-TILE CLASSIFICATION (T1.d)
|
// TRIVIAL-TILE CLASSIFICATION (T1.d)
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// Verdict de ClassifyTile pour une tuile AVANT le pré-échantillonnage 33³+ :
|
// Verdict de ClassifyTile pour une tuile AVANT le pré-échantillonnage 33³+ :
|
||||||
// AllSolid / AllAir garantissent que CHAQUE point du treillis du mesher (marge
|
// AllSolid / AllAir guarantee every core cell vertex of the mesher (g=0..Cells;
|
||||||
// ±1 incluse) est du même côté de l'iso ⇒ maillage vide, GenerateMesh est
|
// the ±1 halo is normal-only) is on the same side of the iso ⇒ empty mesh, GenerateMesh is
|
||||||
// sautée. Mixed = "je ne peux pas le prouver" ⇒ génération normale. Un faux
|
// sautée. Mixed = "je ne peux pas le prouver" ⇒ génération normale. Un faux
|
||||||
// Mixed coûte juste du CPU ; un faux AllSolid/AllAir ferait un TROU — les
|
// Mixed coûte juste du CPU ; un faux AllSolid/AllAir ferait un TROU — les
|
||||||
// verdicts ne sont donc émis que sur des bornes exactes (colonnes surface
|
// verdicts ne sont donc émis que sur des bornes exactes (colonnes surface
|
||||||
|
|||||||
@@ -16,7 +16,12 @@
|
|||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class URealtimeMeshComponent;
|
class URealtimeMeshComponent;
|
||||||
|
class URealtimeMesh;
|
||||||
class URealtimeMeshSimple;
|
class URealtimeMeshSimple;
|
||||||
|
class UBodySetup;
|
||||||
|
class APawn;
|
||||||
|
class UPawnMovementComponent;
|
||||||
|
class UCharacterMovementComponent;
|
||||||
class UVoxelDiffLayer;
|
class UVoxelDiffLayer;
|
||||||
class UVoxelContentManager;
|
class UVoxelContentManager;
|
||||||
class UVoxelAtmosphereManager;
|
class UVoxelAtmosphereManager;
|
||||||
@@ -81,18 +86,18 @@ struct FChunkResult
|
|||||||
// so the game thread only uploads them — the per-vertex builder loop was the dominant
|
// so the game thread only uploads them — the per-vertex builder loop was the dominant
|
||||||
// game-thread cost while moving (the apply drain). TSharedPtr (not a by-value StreamSet) so
|
// game-thread cost while moving (the apply drain). TSharedPtr (not a by-value StreamSet) so
|
||||||
// FChunkResult stays movable through the MPSC queue with the type only FORWARD-DECLARED here.
|
// FChunkResult stays movable through the MPSC queue with the type only FORWARD-DECLARED here.
|
||||||
// Null ⇒ empty/all-air tile (no component).
|
// Null ⇒ no-surface tile (uniform all-air/all-solid or an empty content band; no component).
|
||||||
TSharedPtr<RealtimeMesh::FRealtimeMeshStreamSet> Streams;
|
TSharedPtr<RealtimeMesh::FRealtimeMeshStreamSet> Streams;
|
||||||
uint32 Epoch = 0; // Generation epoch — discard if stale
|
uint32 Epoch = 0; // Generation epoch — discard if stale
|
||||||
// Monotonic request timestamp used only for streaming telemetry. It is carried through the
|
// Monotonic request timestamp used only for streaming telemetry. It is carried through the
|
||||||
// worker queue so the profile can report request-to-ready, not just worker generation time.
|
// worker queue so the profile can report request-to-apply, not just worker generation time.
|
||||||
uint64 RequestStartCycles = 0;
|
uint64 RequestStartCycles = 0;
|
||||||
// Worker timing boundaries for separating scheduler wait from generation and result/apply wait.
|
// Worker timing boundaries for separating scheduler wait from generation and result/apply wait.
|
||||||
// These are telemetry-only and remain zero when tile profiling is disabled.
|
// These are telemetry-only and remain zero when tile profiling is disabled.
|
||||||
uint64 GenerationStartCycles = 0;
|
uint64 GenerationStartCycles = 0;
|
||||||
uint64 GenerationEndCycles = 0; int32 ClassifyVerdict = -1; bool bClassifierCacheHit = false; bool bClassifierRegionHit = false; double ClassifySeconds = 0.0; double MeshSeconds = 0.0; double StreamSeconds = 0.0; int32 NumTriangles = 0;
|
uint64 GenerationEndCycles = 0; int32 ClassifyVerdict = -1; bool bClassifierCacheHit = false; bool bClassifierRegionHit = false; double ClassifySeconds = 0.0; double MeshSeconds = 0.0; double StreamSeconds = 0.0; int32 NumTriangles = 0;
|
||||||
bool bAborted = false; // Worker observed shutdown; never mark this tile loaded
|
bool bAborted = false; // Worker observed shutdown; never mark this tile loaded
|
||||||
bool bEmpty = true; // true ⇒ all-air tile (Streams null); still marked loaded so we don't re-submit
|
bool bEmpty = true; // true ⇒ no surface (uniform all-air/all-solid or empty band); still loaded
|
||||||
// F17 — the mesher classifies every triangle semantically (sky-cap = down-facing near the
|
// F17 — the mesher classifies every triangle semantically (sky-cap = down-facing near the
|
||||||
// column's CeilSurf; overhangs/cave roofs stay ground) and packs them as two contiguous runs
|
// column's CeilSurf; overhangs/cave roofs stay ground) and packs them as two contiguous runs
|
||||||
// (ground then cap) in the index buffer → polygroups 0/1 → two RMC sections with their own
|
// (ground then cap) in the index buffer → polygroups 0/1 → two RMC sections with their own
|
||||||
@@ -193,9 +198,41 @@ public:
|
|||||||
// game-thread problem (this supersedes the earlier region-batching). Collision + content
|
// game-thread problem (this supersedes the earlier region-batching). Collision + content
|
||||||
// are level-0 only.
|
// are level-0 only.
|
||||||
|
|
||||||
/** Tiles fully loaded — INCLUDING empty/all-air tiles, so we never re-submit them. */
|
/** Tiles whose result was successfully applied — INCLUDING empty/all-air tiles, so we never
|
||||||
|
* re-submit them. A non-empty level-0 tile is not collision-ready until its RMC cook callback
|
||||||
|
* has added it to CollisionReadyTiles. */
|
||||||
TSet<FVoxelTileKey> LoadedTiles;
|
TSet<FVoxelTileKey> LoadedTiles;
|
||||||
|
|
||||||
|
/** Level-0 tiles whose latest submitted RMC collision body has finished cooking and was
|
||||||
|
* installed. This is the authoritative collision-readiness set used by the pawn gate; mesh
|
||||||
|
* submission and LoadedTiles are intentionally not sufficient. */
|
||||||
|
TSet<FVoxelTileKey> CollisionReadyTiles;
|
||||||
|
|
||||||
|
/** Level-0 tiles whose applied result is known to contain no surface (uniform empty/solid).
|
||||||
|
* They need no BodySetup themselves, but the pawn gate may walk past them while looking for
|
||||||
|
* the first resolved collidable support tile. */
|
||||||
|
TSet<FVoxelTileKey> CollisionNotRequiredTiles;
|
||||||
|
|
||||||
|
/** Subset of CollisionNotRequiredTiles proved all-solid. A support probe inside one of these
|
||||||
|
* tiles searches upward for the boundary tile; all-air/empty tiles search downward. */
|
||||||
|
TSet<FVoxelTileKey> CollisionSolidTiles;
|
||||||
|
|
||||||
|
struct FPendingCollisionCook
|
||||||
|
{
|
||||||
|
TWeakObjectPtr<URealtimeMesh> Mesh;
|
||||||
|
uint64 SubmissionId = 0;
|
||||||
|
uint64 MeshSubmittedCycles = 0;
|
||||||
|
uint64 CollisionSubmittedCycles = 0;
|
||||||
|
bool bBodyUpdatedEventSeen = false;
|
||||||
|
uint64 BodyUpdatedEventCycles = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** One serialised completion record per tile. The serial prevents a late RMC future from
|
||||||
|
* making a remeshed tile, or a pooled component reused by another tile, appear ready. */
|
||||||
|
TMap<FVoxelTileKey, FPendingCollisionCook> PendingCollisionCooks;
|
||||||
|
uint64 NextCollisionSubmissionId = 0;
|
||||||
|
TMap<URealtimeMesh*, FDelegateHandle> CollisionBodyUpdatedHandles;
|
||||||
|
|
||||||
/** Render component per NON-empty loaded tile (GC-safe via actor ownership; not UPROPERTY
|
/** Render component per NON-empty loaded tile (GC-safe via actor ownership; not UPROPERTY
|
||||||
* because FVoxelTileKey isn't a USTRUCT key). */
|
* because FVoxelTileKey isn't a USTRUCT key). */
|
||||||
TMap<FVoxelTileKey, URealtimeMeshComponent*> TileComponents;
|
TMap<FVoxelTileKey, URealtimeMeshComponent*> TileComponents;
|
||||||
@@ -640,9 +677,11 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Game-thread apply for one gen result (shared by ProcessPendingChunks + SyncRemeshTile):
|
* Game-thread apply for one gen result (shared by ProcessPendingChunks + SyncRemeshTile):
|
||||||
* epoch check, mark loaded, ingest capture, then either release the tile's component (empty) or
|
* epoch check, ingest capture, then either release the tile's component (empty) or submit a mesh
|
||||||
* ApplyMeshToTile. Returns true iff a VISIBLE mesh was uploaded (counts against the apply budget).
|
* through ApplyMeshToTile. Returns true iff a VISIBLE mesh was submitted (counts against the
|
||||||
* Does NOT touch PendingTiles — the caller owns that.
|
* apply budget); collision readiness is a later RMC completion.
|
||||||
|
* Does NOT touch PendingTiles — the caller owns that. A non-empty result counts as applied only
|
||||||
|
* after mesh submission succeeds; collision readiness arrives asynchronously afterwards.
|
||||||
*/
|
*/
|
||||||
bool ApplyTileResult(FChunkResult& Result);
|
bool ApplyTileResult(FChunkResult& Result);
|
||||||
|
|
||||||
@@ -681,7 +720,7 @@ public:
|
|||||||
* the strate content band the mesh was cut to; material lookups clamp into that band).
|
* the strate content band the mesh was cut to; material lookups clamp into that band).
|
||||||
* Never called for empty results.
|
* Never called for empty results.
|
||||||
*/
|
*/
|
||||||
void ApplyMeshToTile(FChunkResult& Result);
|
bool ApplyMeshToTile(FChunkResult& Result);
|
||||||
|
|
||||||
/** Mini-sun lighting (bounded directional). Each frame writes the nearest 4 active orbs' WORLD
|
/** Mini-sun lighting (bounded directional). Each frame writes the nearest 4 active orbs' WORLD
|
||||||
* positions (+ reach radius in .w) into OrbLightMPC's Orb0..3 vector params; the Directional
|
* positions (+ reach radius in .w) into OrbLightMPC's Orb0..3 vector params; the Directional
|
||||||
@@ -708,9 +747,39 @@ public:
|
|||||||
// HELPERS
|
// HELPERS
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
|
||||||
/** Get the current player position (or zero if no player) */
|
/** Return the current player position and distinguish "no pawn" from a valid origin position. */
|
||||||
|
bool TryGetPlayerPosition(FVector& OutPosition, APawn** OutPawn = nullptr) const;
|
||||||
|
|
||||||
|
/** Compatibility wrapper for callers that only need the location. A real pawn at the origin
|
||||||
|
* therefore returns FVector::ZeroVector just like any other valid position. */
|
||||||
FVector GetPlayerPosition() const;
|
FVector GetPlayerPosition() const;
|
||||||
|
|
||||||
|
/** Level-0 tile under the pawn's feet. This is deliberately separate from the actor-center tile:
|
||||||
|
* the floor that catches a falling pawn is what must be cooked before movement is released. */
|
||||||
|
bool GetPlayerSupportTile(APawn* Pawn, const FVector& PlayerPosition,
|
||||||
|
FVoxelTileKey& OutSupportTile) const;
|
||||||
|
|
||||||
|
bool IsTileCollisionReady(const FVoxelTileKey& Tile) const;
|
||||||
|
bool IsCollisionReadyFromSupportTile(const FVoxelTileKey& CandidateTile,
|
||||||
|
FVoxelTileKey* OutSupportTile = nullptr) const;
|
||||||
|
bool IsPlayerSupportCollisionReady(APawn* Pawn, const FVector& PlayerPosition,
|
||||||
|
FVoxelTileKey* OutSupportTile = nullptr) const;
|
||||||
|
|
||||||
|
/** Gate pawn movement until its current and, when moving, next support tile have a completed
|
||||||
|
* level-0 RMC collision body. The world tick is installed as the pawn prerequisite in the
|
||||||
|
* implementation (and directly on its movement component) so this check runs before
|
||||||
|
* CharacterMovementComponent physics. */
|
||||||
|
void UpdatePawnCollisionGate(APawn* Pawn, const FVector& PlayerPosition, float DeltaTime);
|
||||||
|
void EngagePawnCollisionGate(APawn* Pawn);
|
||||||
|
void ReleasePawnCollisionGate();
|
||||||
|
|
||||||
|
/** Called by the RMC collision future after async cook + BodySetup installation. */
|
||||||
|
void HandleTileCollisionCookComplete(const FVoxelTileKey& Tile, uint64 SubmissionId,
|
||||||
|
URealtimeMesh* Mesh, uint8 Result);
|
||||||
|
void BindRealtimeMeshCollisionEvent(URealtimeMesh* Mesh);
|
||||||
|
void UnbindRealtimeMeshCollisionEvent(URealtimeMesh* Mesh);
|
||||||
|
void HandleRealtimeMeshCollisionBodyUpdated(URealtimeMesh* Mesh, UBodySetup* BodySetup);
|
||||||
|
|
||||||
// (GetLODForChunk / LODToStep / IsChunkInRange removed — dead since the clipmap
|
// (GetLODForChunk / LODToStep / IsChunkInRange removed — dead since the clipmap
|
||||||
// streaming replaced the distance-LOD scheme; the level lives in FVoxelTileKey.)
|
// streaming replaced the distance-LOD scheme; the level lives in FVoxelTileKey.)
|
||||||
|
|
||||||
@@ -800,6 +869,23 @@ public:
|
|||||||
bool bStartupTraceDesiredRecorded = false;
|
bool bStartupTraceDesiredRecorded = false;
|
||||||
TArray<FVoxelTileKey> DesiredSorted; // desired tiles, nearest-first
|
TArray<FVoxelTileKey> DesiredSorted; // desired tiles, nearest-first
|
||||||
|
|
||||||
|
// PLAYER COLLISION GATE — the pawn is allowed to enter/leave a tile only when the level-0
|
||||||
|
// collision body covering its feet has completed the RMC cook. The movement component state is
|
||||||
|
// saved so a temporary stream wait does not permanently change a character's movement mode.
|
||||||
|
TWeakObjectPtr<APawn> PawnTickPrerequisite;
|
||||||
|
TWeakObjectPtr<UPawnMovementComponent> PawnMovementTickPrerequisite;
|
||||||
|
TWeakObjectPtr<APawn> CollisionGatedPawn;
|
||||||
|
TWeakObjectPtr<UPawnMovementComponent> CollisionGatedPawnMovement;
|
||||||
|
TWeakObjectPtr<UCharacterMovementComponent> CollisionGatedCharacterMovement;
|
||||||
|
uint8 SavedCharacterMovementMode = 0;
|
||||||
|
uint8 SavedCharacterCustomMovementMode = 0;
|
||||||
|
bool bSavedCharacterMovementMode = false;
|
||||||
|
bool bSavedPawnMovementActive = false;
|
||||||
|
bool bPawnCollisionGateEngaged = false;
|
||||||
|
bool bPawnGateWaitingForPredictedSupport = false;
|
||||||
|
FVoxelTileKey PawnGatePredictedSupportTile;
|
||||||
|
bool bPawnGateUnsupportedReported = false;
|
||||||
|
|
||||||
// Desired-set membership STAMPÉE : clé → numéro du dernier crossing où la tuile était désirée.
|
// Desired-set membership STAMPÉE : clé → numéro du dernier crossing où la tuile était désirée.
|
||||||
// BuildDesiredTiles upserte le stamp courant puis balaie la map UNE fois : les entrées à stamp
|
// BuildDesiredTiles upserte le stamp courant puis balaie la map UNE fois : les entrées à stamp
|
||||||
// périmé sont les "leavers" (retirées + renvoyées). Le cull ne considère que ces leavers + la
|
// périmé sont les "leavers" (retirées + renvoyées). Le cull ne considère que ces leavers + la
|
||||||
|
|||||||
Reference in new issue
Block a user