fix(streaming): block login on complete world reveal
Make initial login and portal arrival consume one WorldRevealReadinessBarrier that joins near-tier mesh publication, destination composite uploads, and collision residency before normal world geometry becomes visible. This ports retail SmartBox's blocking-cell completion edge into the asynchronous client instead of exposing a ground-only login. Add focused outdoor, indoor, texture, and invalid-claim tests; update the retail pseudocode, architecture, divergence record, issue ledger, roadmap baseline, and synchronized agent guidance. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
6c3bd4ce4b
commit
b60cb67009
14 changed files with 360 additions and 102 deletions
96
src/AcDream.App/Streaming/WorldRevealReadinessBarrier.cs
Normal file
96
src/AcDream.App/Streaming/WorldRevealReadinessBarrier.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
namespace AcDream.App.Streaming;
|
||||
|
||||
/// <summary>
|
||||
/// Joins the render-publication, texture, and collision domains that must be
|
||||
/// complete before the normal world viewport is revealed. Retail keeps
|
||||
/// <c>SmartBox::position_update_complete</c> false while
|
||||
/// <c>CellManager::blocking_for_cells</c> is set
|
||||
/// (<c>SmartBox::UseTime</c>, 0x00455410). acdream loads those domains
|
||||
/// asynchronously, so login and portal arrival must share this explicit
|
||||
/// equivalent of retail's single blocking-cell edge.
|
||||
/// </summary>
|
||||
internal sealed class WorldRevealReadinessBarrier
|
||||
{
|
||||
internal const int OutdoorNeighborhoodRadius = 1;
|
||||
|
||||
private readonly Func<uint, int, bool> _isRenderNeighborhoodReady;
|
||||
private readonly Func<uint, bool> _isSpawnCellReady;
|
||||
private readonly Func<uint, int, bool> _isTerrainNeighborhoodReady;
|
||||
private readonly Func<bool> _areCompositeTexturesReady;
|
||||
private readonly Action<uint, int> _prepareCompositeTextures;
|
||||
private readonly Action _invalidateCompositeTextures;
|
||||
private readonly Func<uint, bool> _isSpawnClaimUnhydratable;
|
||||
|
||||
public WorldRevealReadinessBarrier(
|
||||
Func<uint, int, bool> isRenderNeighborhoodReady,
|
||||
Func<uint, bool> isSpawnCellReady,
|
||||
Func<uint, int, bool> isTerrainNeighborhoodReady,
|
||||
Func<bool> areCompositeTexturesReady,
|
||||
Action<uint, int> prepareCompositeTextures,
|
||||
Action invalidateCompositeTextures,
|
||||
Func<uint, bool> isSpawnClaimUnhydratable)
|
||||
{
|
||||
_isRenderNeighborhoodReady = isRenderNeighborhoodReady
|
||||
?? throw new ArgumentNullException(nameof(isRenderNeighborhoodReady));
|
||||
_isSpawnCellReady = isSpawnCellReady
|
||||
?? throw new ArgumentNullException(nameof(isSpawnCellReady));
|
||||
_isTerrainNeighborhoodReady = isTerrainNeighborhoodReady
|
||||
?? throw new ArgumentNullException(nameof(isTerrainNeighborhoodReady));
|
||||
_areCompositeTexturesReady = areCompositeTexturesReady
|
||||
?? throw new ArgumentNullException(nameof(areCompositeTexturesReady));
|
||||
_prepareCompositeTextures = prepareCompositeTextures
|
||||
?? throw new ArgumentNullException(nameof(prepareCompositeTextures));
|
||||
_invalidateCompositeTextures = invalidateCompositeTextures
|
||||
?? throw new ArgumentNullException(nameof(invalidateCompositeTextures));
|
||||
_isSpawnClaimUnhydratable = isSpawnClaimUnhydratable
|
||||
?? throw new ArgumentNullException(nameof(isSpawnClaimUnhydratable));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new reveal lifetime. Texture readiness is destination-scoped;
|
||||
/// carrying the prior login/portal result across a new destination would
|
||||
/// open the barrier before that destination's composites were uploaded.
|
||||
/// </summary>
|
||||
public void Begin() => _invalidateCompositeTextures();
|
||||
|
||||
/// <summary>
|
||||
/// Advances render-thread texture preparation after all required static
|
||||
/// meshes for the destination neighborhood have been published.
|
||||
/// </summary>
|
||||
public void Prepare(uint destinationCell)
|
||||
{
|
||||
if (destinationCell == 0 || _isSpawnClaimUnhydratable(destinationCell))
|
||||
return;
|
||||
|
||||
int radius = RequiredRenderRadius(destinationCell);
|
||||
if (_isRenderNeighborhoodReady(destinationCell, radius))
|
||||
_prepareCompositeTextures(destinationCell, radius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True only when the same destination is drawable and collidable. An
|
||||
/// impossible indoor claim intentionally crosses the barrier so the
|
||||
/// existing loud forced-placement path can expose the invalid server data.
|
||||
/// </summary>
|
||||
public bool IsReady(uint destinationCell)
|
||||
{
|
||||
if (destinationCell == 0)
|
||||
return false;
|
||||
if (_isSpawnClaimUnhydratable(destinationCell))
|
||||
return true;
|
||||
|
||||
int radius = RequiredRenderRadius(destinationCell);
|
||||
if (!_isRenderNeighborhoodReady(destinationCell, radius)
|
||||
|| !_areCompositeTexturesReady())
|
||||
return false;
|
||||
|
||||
return IsIndoor(destinationCell)
|
||||
? _isSpawnCellReady(destinationCell)
|
||||
: _isTerrainNeighborhoodReady(destinationCell, radius);
|
||||
}
|
||||
|
||||
private static int RequiredRenderRadius(uint destinationCell) =>
|
||||
IsIndoor(destinationCell) ? 0 : OutdoorNeighborhoodRadius;
|
||||
|
||||
private static bool IsIndoor(uint cellId) => (cellId & 0xFFFFu) >= 0x0100u;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue