diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index 939cde0..b6f0eac 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -30,6 +30,7 @@ | 9.2 | Back-face culling in translucent pass (fixes lifestone crystal) | Visual ✓ | | A.1 | Streaming landblock loader — runtime-configurable visible window (default 5×5, `ACDREAM_STREAM_RADIUS`), camera-centered offline / player-centered live, hysteresis-based unloads, pending-spawn list for late CreateObject events | Live ✓ | | A.2 | Frustum culling — per-landblock AABB test (Gribb-Hartmann), terrain + static-mesh renderers skip culled landblocks, perf overlay in window title | Visual ✓ | +| A.3 | Background net receive thread — dedicated daemon thread buffers UDP into Channel, render thread drains | Visual ✓ | Plus polish that doesn't get its own phase number: - FlyCamera default speed lowered + Shift-to-boost @@ -48,7 +49,7 @@ Plus polish that doesn't get its own phase number: **Sub-pieces:** - **✓ SHIPPED — A.1 — Streaming landblock loader.** Runtime-configurable visible window (default 5×5, `ACDREAM_STREAM_RADIUS` env var override). Center follows the fly camera offline and the server-sent player position in live mode. Currently runs **synchronously** on the render thread — the original async-worker design hit DatCollection's lack of thread safety and was reverted for correctness. The Channel-based outbox API is preserved so async loading can return cleanly when `Phase A.3` introduces a thread-safe dat wrapper. Pending-spawn list in `GpuWorldState` parks live `CreateObject` events whose target landblock hasn't been streamed in yet and back-fills them when it arrives, so spawn-vs-streaming races are no longer racy at all. `MaxCompletionsPerFrame=4` spreads the 5×5 first-frame load over ~7 frames (~116ms) to avoid GPU OOM. - **✓ SHIPPED — A.2 — Frustum culling.** Per-landblock AABB test (Gribb-Hartmann plane extraction + positive-vertex AABB test) in both `TerrainRenderer.Draw` and `StaticMeshRenderer.Draw`. Per-entity culling deferred. LOD deferred to Phase C. Performance overlay in window title shows FPS, frame time, visible/total landblock ratio, entity count, animated count. ~160fps uncapped at 5×5 radius. -- **A.3 — Background net I/O thread.** `WorldSession` runs its receive loop on a dedicated thread; parsed game messages are posted to a concurrent queue the render thread drains from `OnUpdate`. Event invocations still happen on the render thread (preserves existing handler assumptions). Removes packet drops under frame stalls. +- **✓ SHIPPED — A.3 — Background net receive thread.** Dedicated daemon thread continuously pulls raw UDP datagrams from the kernel buffer into a `Channel`. Render thread's `Tick()` drains the channel. All decode, fragment assembly, ISAAC crypto, event dispatch, and ack-sending remain on the render thread — minimal change that prevents packet drops during frame stalls. Thread starts after `EnterWorld()` completes; `PumpOnce()` during handshake still reads the socket directly. - **A.4 — Async dat decoding.** Folded into the streaming worker — it's the worker's read path, not a separate subsystem. Called out here because regressions in dat caching could land on this surface. **Acceptance:**