From 4b01c95ecbb060ae99ea31a288059f56330802ce Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 11 Apr 2026 23:13:25 +0200 Subject: [PATCH] =?UTF-8?q?fix(app):=20Phase=20A.1=20=E2=80=94=20run=20Str?= =?UTF-8?q?eamingController.Tick=20before=20WorldSession.Tick?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fourth Phase A.1 hotfix. Terrain rendered correctly after the 0xFFFF fix and the canonicalize fix, but live NPCs and weenies remained invisible. Root cause: in OnUpdate the live session was draining its incoming-message queue BEFORE the streaming controller had a chance to load the initial 5×5 window. The first frame's order was: 1. _liveSession.Tick() — drains the post-login CreateObject flood (40+ NPCs + items at the player's spawn landblock) 2. _streamingController.Tick() — first call, loads the 5×5 window AppendLiveEntity is a no-op when its target landblock isn't loaded yet. So all 40+ spawns landed in step 1 before any landblock existed in GpuWorldState, were silently dropped, and never came back even after the landblocks finished loading in step 2. Fix: swap the order. Streaming runs first so the initial window exists in GpuWorldState before any CreateObject events drain. This is correct because the streaming Tick is now synchronous (per the 531c9f9 hotfix) — by the time it returns, all landblocks in the window are loaded and ready for AppendLiveEntity to find them. A more robust solution would be a pending-spawn list inside GpuWorldState that backfills entities when their landblock loads later. That stays as a future improvement; the simple reorder is correct for the dominant case (login → flood of spawns into the already-known initial landblock). 212 tests green. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 8636f38..53e7e60 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -1238,14 +1238,13 @@ public sealed class GameWindow : IDisposable private void OnUpdate(double dt) { - // Drain any pending live-session traffic. Non-blocking — returns - // immediately if no datagrams are in the kernel buffer. Fires - // EntitySpawned events synchronously on this thread. - _liveSession?.Tick(); - - // Phase A.1: advance the streaming controller. Computes the observer's - // current landblock coordinates and feeds new load/unload diffs to the - // streamer, then drains any completed landblocks into GpuWorldState. + // Phase A.1: advance the streaming controller FIRST so the initial + // landblocks are loaded into GpuWorldState before live-session + // CreateObject events drain. The earlier order (live tick first, + // streaming tick second) caused the initial CreateObject flood from + // login to land before any landblock was loaded; AppendLiveEntity + // is a no-op for unloaded landblocks, so all 40+ NPCs/weenies were + // silently dropped on the first frame and never rendered. if (_streamingController is not null && _cameraController is not null) { int observerCx = _liveCenterX; @@ -1274,6 +1273,12 @@ public sealed class GameWindow : IDisposable _streamingController.Tick(observerCx, observerCy); } + // Drain pending live-session traffic AFTER streaming so any incoming + // CreateObject events find their landblock already loaded in + // GpuWorldState. Non-blocking — returns immediately if no datagrams + // are in the kernel buffer. Fires EntitySpawned events synchronously. + _liveSession?.Tick(); + if (_cameraController is null || _input is null) return; if (!_cameraController.IsFlyMode) return;