# EnvCell landblock transaction — dungeon-login texture loss ## Symptom and captured evidence Logging the `lundberg` account directly into starter dungeon landblock `0x8C04FFFF` produced an untextured/incomplete world after relaunch. The capture in `logs/lundberg-dungeon-relaunch-20260713-182018.out.log` established the ordering: ```text streaming: dungeon collapse -> 0x8C04FFFF streaming: dungeon collapse -> 0x8C04FFFF [late-register] ... 1123 already committed [finalize-replace] ... DISCARDING 1123 committed instances, replacing with 11 pending ``` The old renderer diagnostics came from commit `0a38d934`. They proved that the first job completed a full dungeon and a second job then overwrote it with the small tail of a concurrently mutated pending list. This was not a texture decode or GPU upload failure: the complete cell-shell set was discarded before draw. ## Root causes There were two causes, both required for the observed deterministic failure: 1. Commit `9b06a9b8` added a post-login `ForceReloadWindow` to recover from a guessed-center streaming window. Commit `fa9aedca` later added `StreamingReadinessGate`, which prevents that guessed window from being built at all, but the force reload remained. A sealed-dungeon login therefore pre-collapsed once during spawn and collapsed a second time after the forced reset. 2. The streaming worker published partial state while it was still building. `EnvCellRenderer.RegisterCell` appended to landblock-owned pending lists and a global `_pendingCells` bag collected portal cells. The render thread drained those mutable collections independently. Neither collection identified the streaming job that produced an item, so duplicate and concurrent landblock completions could consume or replace each other's state. The second cause is the architectural defect. Removing only the duplicate load would make the current reproduction rarer, but leave the race available to normal unload/reload and promotion timing. ## Retail/WB boundary No retail gameplay algorithm changes here. The cell portal construction remains the existing mechanical port of retail `PView::InitCell @ 0x005A4B70`, including the dat `PortalSide` bit and reciprocal portal indexing. Geometry-id arithmetic remains the verbatim WorldBuilder `EnvCellRenderManager.cs:94-103` port. This change is an acdream streaming ownership correction: retail does not expose partially hydrated cell arrays to its draw traversal, while acdream must bridge a background CPU loader to a single OpenGL/render thread. ## Transaction design Worker-side pseudocode: ```text builder = new EnvCellLandblockBuildBuilder(landblockId) for each EnvCell in this one landblock: read dat cell + CellStruct append visibility cell to builder-local storage append drawable shell placement to builder-local storage build = builder.Build() // one-way publication; immutable snapshot schedule CPU mesh preparation(build) // queue only; no live renderer mutation return LandblockBuild(terrain/entities, build) ``` Render-thread pseudocode: ```text completion = streamer.Drain() validate completion.EnvCells belongs to completion.Landblock replace this landblock's complete CellVisibility set cache this landblock's cell physics structs replace this landblock's complete EnvCellRenderer snapshot publish the LoadedLandblock to GpuWorldState ``` The invariant is: **one streaming completion owns one complete EnvCell payload; only the render thread may publish it to live per-frame state.** A later reload may replace an earlier snapshot, but it can only replace it with another complete snapshot from that exact job—never a shared-list remainder. ## Login sequencing correction `StreamingController.InitializeKnownLoginCenter` now encodes the post-readiness- gate behavior: - sealed dungeon: pre-collapse exactly once before the first normal tick; - outdoor: enqueue nothing until the first tick at the server-confirmed center. The obsolete `_pendingForceReloadWindow` path was deleted. This removes the duplicate dungeon job without adding a timing guard, retry, or suppression flag. ## Regression coverage - A 1,123-shell build produces a 1,123-instance committed snapshot. - Independently built 1,123- and 11-shell transactions cannot share storage. - A builder can publish only once. - Visibility replacement affects only the matching landblock and rejects a foreign cell before mutating existing state. - `LandblockStreamer` carries the exact `LandblockBuild`/EnvCell transaction instance from worker to consumer. - Repeated sealed-dungeon login initialization enqueues one collapse/load; an outdoor login waits for its first correctly centered tick.