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:
Erik 2026-07-20 16:08:03 +02:00
parent 6c3bd4ce4b
commit b60cb67009
14 changed files with 360 additions and 102 deletions

View file

@ -11,8 +11,8 @@ namespace AcDream.App.Input;
/// Why is this its own class? The auto-entry has four independent
/// preconditions (live session reaches <c>InWorld</c>, the player
/// entity has been streamed into the world dictionary, the player
/// movement controller is constructible, and the terrain under the
/// spawn position has streamed in) plus a manual-override path
/// movement controller is constructible, and the initial world is fully
/// drawable and collidable) plus a manual-override path
/// (the user can flip into fly mode before the auto-entry fires —
/// their choice wins). All five interact with each other in a way
/// that's painful to test through GameWindow but trivial here against
@ -44,7 +44,7 @@ public sealed class PlayerModeAutoEntry
private readonly Func<bool> _isLiveInWorld;
private readonly Func<bool> _isPlayerEntityPresent;
private readonly Func<bool> _isPlayerControllerReady;
private readonly Func<bool> _isSpawnGroundReady;
private readonly Func<bool> _isWorldReady;
private readonly Action _enterPlayerMode;
private bool _armed;
@ -62,12 +62,10 @@ public sealed class PlayerModeAutoEntry
/// PlayerMovementController is set up. Stays true once player mode
/// is established; the auto-entry's job is to flip it from false
/// to true exactly once.</param>
/// <param name="isSpawnGroundReady">True iff the terrain under the
/// player's spawn position has streamed into the physics engine.
/// #106 gate-2 (2026-06-09): entering player mode earlier integrates
/// gravity against an empty world and free-falls the player into the
/// void. Retail never has this state — it loads cells synchronously;
/// this hold is the async-streaming equivalent of that invariant.</param>
/// <param name="isWorldReady">True iff the player's destination is
/// complete across render publication, composite textures, and collision.
/// Retail keeps position completion behind one blocking cell-load edge;
/// acdream's asynchronous domains must converge before entry.</param>
/// <param name="enterPlayerMode">Action invoked on the firing
/// tick. The same routine the manual Tab handler invokes (fly →
/// player transition). Must construct the controller + chase
@ -77,13 +75,13 @@ public sealed class PlayerModeAutoEntry
Func<bool> isLiveInWorld,
Func<bool> isPlayerEntityPresent,
Func<bool> isPlayerControllerReady,
Func<bool> isSpawnGroundReady,
Func<bool> isWorldReady,
Action enterPlayerMode)
{
_isLiveInWorld = isLiveInWorld ?? throw new ArgumentNullException(nameof(isLiveInWorld));
_isPlayerEntityPresent = isPlayerEntityPresent ?? throw new ArgumentNullException(nameof(isPlayerEntityPresent));
_isPlayerControllerReady = isPlayerControllerReady ?? throw new ArgumentNullException(nameof(isPlayerControllerReady));
_isSpawnGroundReady = isSpawnGroundReady ?? throw new ArgumentNullException(nameof(isSpawnGroundReady));
_isWorldReady = isWorldReady ?? throw new ArgumentNullException(nameof(isWorldReady));
_enterPlayerMode = enterPlayerMode ?? throw new ArgumentNullException(nameof(enterPlayerMode));
}
@ -117,7 +115,7 @@ public sealed class PlayerModeAutoEntry
if (!_isLiveInWorld()) return false;
if (!_isPlayerEntityPresent()) return false;
if (!_isPlayerControllerReady()) return false;
if (!_isSpawnGroundReady()) return false;
if (!_isWorldReady()) return false;
_armed = false;
_enterPlayerMode();