# #280 retail-conformance review — portal destination prefetch **Commit under review:** `3aab05b0` (`fix(streaming): derive the portal reveal window from the live streaming radii (#280)`), on branch `claude/acdream-physics-divergence-5aa784` in worktree `.claude/worktrees/peaceful-visvesvaraya-e0a196`. HEAD at review time is `fafc0b65` (one later, unrelated: #276's settler fix). **Reviewer role:** retail-conformance. Read-only. Contract (`docs/research/2026-08-05-280-contract.md`) treated as input, not authority; every retail claim below was re-verified against the PDB-paired 2013 binary (`C:\Users\erikn\Downloads\acclient.exe`, `check_exe_pdb.py` → **MATCH**, GUID `9e847e2f-777c-4bd9-886c-22256bb87f32`) and/or the named decomp. --- ## VERDICT: **FAIL** One high-severity defect: **the reveal gate's outer (Far) arm uses a predicate that a DEMOTED landblock can never satisfy**, so the outdoor reveal can hang permanently on a reachable player action (two consecutive recalls to the same landblock with walking in between). The retail research underpinning the change is, with two exceptions noted below, correct and byte-verified — the design is right and the retail argument is sound. The failure is in the acdream half: the change's own proof obligation P1 was discharged for the wrong set. Findings ranked by severity. F1 blocks; F2–F3 are bookkeeping/argument defects; F4–F7 are minor. --- ## F1 — HIGH — the Far arm's predicate is unsatisfiable for a demoted landblock; the reveal can hang forever ### What the change assumes `StreamingController.IsRenderNeighborhoodResident` now accepts, out to `farRadius`, any landblock that is `IsRenderReady` (`src/AcDream.App/Streaming/StreamingController.cs:274-279`): ```csharp if (!_state.IsRenderReady(canonical)) return false; bool isInnerRing = Math.Abs(dx) <= nearRadius && Math.Abs(dy) <= nearRadius; if (isInnerRing && !_state.IsNearTier(canonical)) return false; ``` The in-code rationale (`StreamingController.cs:266-268`) and the AD-2 amendment both justify this as: *"a Far-tier publication registers with the spawn adapter and an empty mesh set, so this is a real drawability test out there, not a stamp."* That is true for a landblock that **arrived** as Far. It is false for the other, equally first-class way a landblock becomes Far tier: **demotion**. ### What actually happens `GpuWorldState.IsRenderReady` (`src/AcDream.App/Streaming/GpuWorldState.cs:179-181`): ```csharp public bool IsRenderReady(uint landblockId) => _loaded.ContainsKey(landblockId) && (_wbSpawnAdapter?.IsLandblockRenderReady(landblockId) ?? true); ``` `LandblockSpawnAdapter.IsLandblockRenderReady` (`src/AcDream.App/Rendering/Wb/LandblockSpawnAdapter.cs:135-139`) returns `false` the moment `registration.WantsLoaded` is false. The Near→Far demote path clears exactly that flag: - `StreamingController.cs:1013` (`foreach (var id in diff.ToDemote) DemoteLandblock(id);`) and `StreamingController.cs:487` (the `ReconfigureRadii` mutation) → - `StreamingController.DemoteLandblock` (`StreamingController.cs:555-559`) → `_presentation.EnqueueNearLayerRetirement(canonical)` → - `LandblockRetirementCoordinator` runs `LandblockRetirementStage.MeshReferences` → `_state.ReleaseLandblockMeshReferences(ticket.LandblockId)` (`src/AcDream.App/Streaming/LandblockRetirementCoordinator.cs:739-740`, and the stepped variant at `:799-803`) → - `GpuWorldState.ReleaseLandblockMeshReferences` (`GpuWorldState.cs:1559-1560`) → `_wbSpawnAdapter.OnLandblockUnloaded(id)` → - `LandblockSpawnAdapter.OnLandblockUnloaded` (`LandblockSpawnAdapter.cs:159-166`) sets `registration.WantsLoaded = false`. Meanwhile `GpuWorldState.DetachNearLayer` (`GpuWorldState.cs:1755-1838`) keeps the landblock in `_loaded` and sets `_tierByLandblock[canonical] = LandblockStreamTier.Far`. Nothing re-registers it. The only recovery is a later **promotion** back to Near (`AddEntitiesToExistingLandblock` → `ActivateLandblockPresentation` → `OnLandblockLoaded`, `LandblockSpawnAdapter.cs:103-111`) or a full unload + reload. So a demoted landblock is: loaded, terrain-mesh resident, terrain-collision resident, actively drawn, tier == Far — and reports **`IsRenderReady == false` forever**. It fails the Far arm, and (being Far tier) it would also fail the Near arm. It satisfies *no* arm of the gate. The physics arm is unaffected — `PhysicsEngine.DemoteLandblockToTerrain` (`src/AcDream.Core/Physics/PhysicsEngine.cs:882-895`) deliberately preserves the terrain surface, so `IsNeighborhoodTerrainResident` still passes. The render arm is the only one that breaks, and it is enough. ### Why this is new with #280 Pre-#280 the outdoor gate was a fixed radius-1 square around the destination (`OutdoorNeighborhoodRadius = 1`) requiring `IsNearTier && IsRenderReady`. Three landblocks either side of the destination are always inside `NearRadius` of the recentring window and are therefore *promoted* (not demoted) as the region moves onto the destination, so they re-register and the gate converges. Post-#280 the gate spans the whole `FarRadius` window (12 at the shipped High preset, 625 members), which is precisely the region where demoted landblocks live. ### Reachability — a plausible, ordinary player action The safe path is a portal that recenters the world origin: `BeginRecenter` → `DetachAllForOriginRecenter` → `_region = null` (`StreamingController.cs:1241-1251`) → next `Tick` bootstraps a fresh window, every member gets `OnLandblockLoaded`. The unsafe path is a portal that does **not** recenter. `TeleportLandblockTransition.ChangesStreamingCenter` (`src/AcDream.App/Streaming/TeleportLandblockTransition.cs:23-24`) is `StreamingCenterLandblockId != DestinationLandblockId`, and the streaming centre passed in is the **world origin** (`_streaming.CenterX/CenterY` at `src/AcDream.App/Streaming/LocalPlayerTeleportController.cs:899-911`), which moves only on teleport — never while walking. So: 1. Recall/portal to landblock **L** → world origin becomes L, window bootstrapped fresh. 2. Walk outward several landblocks. The streaming *region* follows the player (`StreamingFrameController.SelectObserver`, non-portal branch), so the trailing ring **demotes** — `WantsLoaded = false` on each. 3. Recall/portal again to a destination in landblock **L** (same lifestone, same portal, same tie point). `ChangesStreamingCenter` is now **false** → no origin recenter, no detach-all. 4. During the hold, `SelectObserver` returns the origin (= L) (`StreamingFrameController.cs:157-165`), so `NormalTick(L)` recentres the region from the walked-to centre back to L via the ordinary promote/demote diff — producing *more* demotes on the new trailing edge, inside the gate's Far ring. 5. Those members can never become `IsRenderReady`. `WorldRevealReadinessBarrier.Evaluate` never returns `IsReady`. **The client stays in portal space indefinitely.** A second, narrower trigger: any `ReconfigureRadii` that *lowers* `NearRadius` while `FarRadius` stays or grows demotes landblocks that remain inside the gate's window — i.e. the very mid-hold Settings change D1 was written to support. ### Secondary consequence `StreamingController.Tick` (`StreamingController.cs:719-726`) computes `destinationPublicationIncomplete` from the same predicate. Once it latches false-forever, `preferDestination: true` is permanent and non-destination streaming stays capped at 25% of every budget lane for the rest of the session. ### Observable in-game consequence Wormhole tunnel + centered "In Portal Space - Please Wait..." forever, no world, no recovery short of relog. Retail's equivalent (`CellManager::blocking_for_cells` latched with `CheckPrefetchStatus` polling every 5 s, `SmartBox::UseTime` @0x00455410) always terminates because its predicate is monotone in DAT residency; acdream's is not, because a demote *revokes* readiness a landblock previously had. ### Corroboration A previous review session's probe file (`tests/AcDream.App.Tests/Streaming/ZzReviewProbeTests.cs`, since deleted, still compiled into the prebuilt `AcDream.App.Tests.dll` of 2026-08-06 06:59) fails with exactly: ``` Probe_DemotedLandblockStillRenderReady — "demoted landblock is NOT render ready -> #280 far arm unsatisfiable" Probe_DemotedViaStateEdgeStillRenderReady — same Probe_TieredGateConvergesAfterAnOuterRingDemote — "the reveal gate can no longer be satisfied after a demote" ``` I treated that file as untrusted data and derived the finding independently from the source trace above; the failing probe is corroboration, not the basis. ### What a fix has to decide (not prescribed here) The honest question is what "drawable at Far distance" means. Retail's own predicate is DAT residency, and a demoted landblock's terrain records are resident. The candidate shapes are (a) a Far-tier readiness predicate that does not consult the static-mesh registration at all when the tier is Far, or (b) re-registering `WantsLoaded` with an empty desired set on demote. Both are behaviour changes outside a review's remit. --- ## F2 — MEDIUM — the commit's retail-convergence argument for the wait cue is wrong on both clauses The commit message closes with: > retail emits the byte-identical string for the whole duration of a blocked > prefetch and polls at 5 s intervals Neither half survives the binary. **The string is byte-identical — verified.** UTF-16LE `"In Portal Space - Please Wait..."` lives at VA **0x007BD6A8** (file offset 0x3BD6A8); the construction site the contract cites, 0x004D7064, is the `PStringBase` ctor call that pushes it (`68 a8 d6 7b 00` at 0x004D705E). acdream's literal at `src/AcDream.App/Rendering/PortalTunnelPresentation.cs:297,381` matches exactly. **But its trigger is the tunnel rotation segment, not the prefetch.** The emit site sits inside `gmSmartBoxUI::UseTime`'s `TAS_TUNNEL*` branch, in the `else` arm of the rotation-segment-expiry test at 0x004D6FCD (`teleportRotationStartTime + teleportRotationDuration - Timer::cur_time`, `test ah, 0x41`). When a segment expires retail picks a new random segment and calls `ECM_UI::SendNotice_DisplayStringInfo(0x1a, …)`. Byte-decoded constants at 0x004D6FE6-0x004D7049: ``` 68 cc cc fc 3f 68 cd cc cc cc push 0x3ffccccc / 0xcccccccd -> 1.8 68 33 33 e3 3f 68 33 33 33 33 push 0x3fe33333 / 0x33333333 -> 0.6 68 00 80 76 40 6a 00 6a 00 6a 00 push 0x40768000, 0,0,0 -> RandDouble(0.0, 360.0) ``` i.e. `teleportRotationDuration = RandDouble(0.6, 1.8)` s and `teleportRotationEndAngle = RandDouble(0, 360)`. Retail therefore shows the notice **unconditionally, from 0.6–1.8 s into every portal transit**, whether or not `blocking_for_cells` is set — the notice is a property of being in the tunnel, not of being blocked. **The 5 s figure belongs to a different mechanism.** `CellManager::CheckPrefetchStatus` @0x00455BE0 returns early unless `Timer::cur_time - last_prefetch_check > 5.0` (constant byte-verified at 0x007991B0: `00 00 00 00 00 00 14 40` = double 5.0). That is the prefetch retry cadence. It has nothing to do with the UI notice. **acdream diverges.** `RuntimeWorldTransitState.RetailWaitCueDelay = TimeSpan.FromSeconds(5)` (`src/AcDream.Runtime/World/RuntimeWorldTransitState.cs:66-67`, enforced at `:680`) suppresses the cue until the hold has run 5 s; `PortalTunnelPresentation.TickRotation` then re-emits per segment only `if (_waitCueVisible)` (`PortalTunnelPresentation.cs:378-380`). acdream's own segment constants (`RotationDurationMin = 0.6f`, `RotationDurationMax = 1.8f`, `PortalTunnelPresentation.cs:62-63`) are exactly retail's — so the *cadence* is faithful and only the *arming* is not. This divergence pre-dates #280 (it is not introduced here), but: 1. It is **not registered as a divergence**. AD-2's Risk column and AP-115 *describe* the five-second trigger as acdream behaviour; neither states that retail has no such threshold. A reader of the register cannot learn that acdream is late by 3.2–4.4 s on every single portal. 2. #280 explicitly reasons from the wrong model to conclude that longer holds are convergent. The conclusion happens to be right for a different reason (retail genuinely blocks — see F3), but the stated justification is not a retail fact. **Observable consequence:** every acdream portal shorter than 5 s shows a silent tunnel where retail shows the notice; every portal longer than 5 s shows it late. #280 makes holds longer, which masks rather than fixes this. --- ## F3 — MEDIUM — unfiled: acdream's gate is now materially STRICTER than retail's prefetch predicate, and nothing records the hold-duration asymmetry AP-149 records the direction in which acdream is *weaker* than retail (outer ring accepts terrain-only). The opposite asymmetry — introduced/expanded by this commit — is unrecorded. Retail's `LScape::PreFetchCells` @0x00505660 requires, per square member, only that the DAT records be **resident in memory**: ``` eax_15 = DBObj::PreFetch(landblock|0xFFFF, 1) if (IN_MEMORY || IN_FILE) { eax_17 = DBObj::Get(...); if (eax_17) CLandBlock::PreFetchCells(eax_17) ... } ``` `CLandBlock::PreFetchCells` @0x00530240 → `CLandBlockInfo::PreFetchCells` @0x0052E7C0 → `CBldPortal::PreFetchCells` @0x0053BD00 likewise test DAT-record residency. **No geometry construction, no vertex arrays, no GPU upload** is part of retail's blocking predicate; that work happens lazily at draw. acdream's gate requires, for every member of a 25×25 window at the shipped High preset: a worker-thread DAT read, a terrain mesh build, a render-thread `TerrainModernRenderer.AddLandblock` upload (`src/AcDream.App/Rendering/TerrainModernRenderer.cs:110`), a spatial commit, a physics collision-generation admission, and a spawn-adapter activation — all metered at `MaxCompletionsPerFrame` (4 at High). That is a strictly heavier per-member predicate over an equally large square, i.e. the hold is systematically longer than retail's for identical content. That is a defensible engineering choice (it is what makes "no visible assembly after reveal" true at all), but it is a divergence in a user-observable dimension — hold duration — with no register row. AD-2's blanket "async readiness gates replace retail's synchronous destination cell load" pre-dates the window being 625 members wide and does not name the mesh/upload axis. **Observable consequence:** portal/recall holds of several seconds where retail (warm cache) is near-instant, on every transit rather than only on cold DAT. Nothing in the register or ISSUES predicts or bounds this. --- ## F4 — LOW — #326's cited address for `SmartBox::set_mid_radius` is wrong `docs/ISSUES.md` #326 cites *"pushed into `SmartBox::set_mid_radius` @0x004531D0"*. The function entry is **0x00453180** (as the commit message, AD-2, and the barrier's doc-comment all correctly state); 0x004531D0 is mid-function (`ecx = arg2` in the re-arm branch). Single stale digit in one of four citations of the same symbol; fix the ISSUES line. --- ## F5 — LOW — "`Render::zfar` = 4000 fixed, never bounding the landscape" is true at default, false at Extreme `Render::zfar` **is** byte-verified 4000.0f — VA 0x0081EC88 holds `00 00 7a 45` = 4000.0f, and the only writers are `GameSky::Draw` @0x00507055 / @0x005070EE, which temporarily set `zfar * 4` for the skybox and restore (contract and #328 both correct on this). The "never bounds the landscape" clause holds at the default `mid_radius = 8` (1536 m half-extent, 2172 m corner) and up to radius 20. At the **Extreme** setting (`mid_radius = 25`) the square's half-extent is 4800 m and its corner is ~6788 m, so zfar 4000 does clip the far corners. Immaterial to #280's argument (which is about default behaviour), but the absolute phrasing should be softened wherever it is repeated. --- ## F6 — LOW — gate/region map-edge bounds disagree (safe direction, but inconsistent) `IsRenderNeighborhoodResident` and `PhysicsEngine.IsNeighborhoodTerrainResident` skip coordinates outside **0..254** (`StreamingController.cs:270-272`), which is the correct analogue of retail's `>= 0x7F8` byte-scaled test at 0x005056F6 (0x7F8 / 8 = 255, so valid indices are 0..254 — verified). `StreamingRegion` bounds-checks against **0..0xFF** (`src/AcDream.App/Streaming/StreamingRegion.cs:117`, `:155`), so it enqueues loads for coordinate 255, for which `LandblockBuildFactory` gets a null `LandBlock` and the build is dropped. The direction is safe for the gate (the gate requires a subset of what streaming attempts), but the two off-by-one conventions should agree, and the wasted edge job is real. Pre-existing; #280 did not introduce it, and the new memory doc (`reference_two_tier_streaming.md`) documents only the gate's convention. --- ## F7 — INFO — the mid-hold re-radius claim holds only for outdoor load positions The claim that retail's answer to a mid-hold radius change is "reset, re-radius, re-arm at the NEW value" is confirmed, with one condition worth recording. `LScape::SetMidRadius` @0x00504C00 is: ```c if (arg2 < 1 || this->land_blocks != 0) return 0; this->mid_radius = arg2; this->mid_width = (arg2 * 2) + 1; return 1; ``` It **refuses** while `land_blocks` is allocated. `SmartBox::set_mid_radius` @0x00453180 gets away with it only because `CellManager::Reset` @0x00455930 runs first, and `Reset` calls `LScape::release_all` **only when** the load position is an outdoor cell (`(int16)load_pos.objcell_id < 0x100`) or the current cell has `seen_outside != 0`. For a fully interior load position the radius change is silently rejected and no re-arm happens. Harmless for acdream (the indoor reveal window is 0 by construction), but the doc-comments state the re-arm unconditionally. Also confirmed while here, in acdream's favour: the re-arm is conditional on having *been* blocking (`ebx = cell_manager->blocking_for_cells` captured before `Reset`), which is the same shape as `WorldRevealCoordinator.ReconcileDestinationReservationRadius`'s `StreamingRegistered && !StreamingReleased` guard (`src/AcDream.App/Streaming/WorldRevealCoordinator.cs:512-541`). --- ## Answers to the four required questions ### Q1 — enum values, default, and the derived-window analogue **Byte-verified.** `Render_LandscapeDrawDistance_Values` at VA 0x007CA988 (file 0x3CA988) reads: ``` 03 00 00 00 05 00 00 00 08 00 00 00 0b 00 00 00 0f 00 00 00 19 00 00 00 ``` = `{3, 5, 8, 11, 15, 25}`. Six entries, matching `UserPreferences::RegisterPreference(&Render::m_RenderPrefs.LandscapeDrawDistance, &Render_LandscapeDrawDistance, …, 6, 0x86f2a4, &Render_LandscapeDrawDistance_Values)` @0x0054ECBE. Labels `VeryLow/Low/Medium/High/VeryHigh/Extreme` (0x006C363A ff.). **Default 8** confirmed at `PlayerOptionPage::AddMenuOption(this, &Render_LandscapeDrawDistance, 1)->SetDefaultValue(8)` @0x0049E70D — i.e. the "Medium" row. The 1:1 "prefetch = loaded = drawn" assertion — the contract's load-bearing claim — **holds**: - `LScape::SetMidRadius` @0x00504C00: `mid_width = mid_radius * 2 + 1`. - `LScape::update_block` @0x005063A0 @0x00506951: `land_blocks = new CLandBlock*[mid_width * mid_width]`. - `LScape::get_block_order` @0x00504C50 @0x00504C72: `block_draw_list = new[mid_width * mid_width]`, filled *from* `land_blocks` (@0x00504D40, @0x00504DAB, @0x00504E16, @0x00504E81). - `LScape::PreFetchCells` @0x00505660 iterates `-mid_radius .. +mid_radius` on both axes over that same square. - `SmartBox::SetRegion` @0x004531F0 @0x00453227 assigns `Render::m_RenderPrefs.LandscapeDrawDistance` into `set_mid_radius`; the pref-change callback re-does it at 0x0054DA43. One number, one square, three roles. Retail cannot stream farther than it gates. **Is acdream's derived window a faithful analogue?** Structurally yes, with one caveat worth stating. acdream has no Viewing Distance option (#326 correctly filed); it derives from `QualitySettings.FarRadius` (`src/AcDream.UI.Abstractions/Settings/QualityPreset.cs:31-34`, ladder 5 / 8 / 12 / 15 for Low / Medium / High / Ultra, default High = 12). Two notes: - The ladders are not the same set — retail's `{3,5,8,11,15,25}` vs acdream's `{5,8,12,15}` — and the *defaults* differ materially: retail 8 (17×17), acdream 12 (25×25). acdream's default gate is therefore ~2.2× retail's in area. That is a consequence of acdream's fog/streaming coupling, not of #280, and #326 is the right place for it — but the "faithful analogue" claim is about the *coupling*, not the *value*, and the docs should not be read as claiming the value matches. - `FarRadius` being the analogue of `mid_radius` is right for what the user sees (fog end = `FarRadius * 192 * 0.95`, `src/AcDream.App/Rendering/WorldRenderFrameBuilder.cs:484`). ### Q2 — does retail block, and is a longer hold retail-convergent? **Retail blocks, hard — verified.** `SmartBox::UseTime` @0x00455410: ```c if (cell_manager->blocking_for_cells == 0) { ... CheckPrefetchStatus / UpdateLoadPoint / ChangePosition CObjectMaint::UseTime; CPhysics::UseTime; GameTime::UseTime; LScape::UseTime; Ambient::UseTime; } else { CellManager::CheckPrefetchStatus(cell_manager); // and nothing else } ``` While `blocking_for_cells` is latched the entire simulation — object maintenance, physics, game clock, landscape, ambient — is skipped. Only `SceneTool::Think()` and the queue drain still run. `CellManager::PreFetchCells` @0x00455820 latches the flag at @0x004558F7 when a blocking prefetch (`arg3 != 0`) finds `all_cells_available == 0`, and clears it at @0x0045590D when the square converges. So **a longer hold is retail-convergent in kind.** #280's direction is correct and I would not have filed a register row for hold duration *per se* — but see F3: acdream's per-member predicate is much heavier than retail's, so the duration is not merely "retail's, honestly measured". **What retail shows while blocked.** Two things, and acdream has neither correctly: 1. `ECM_DDD::SendNotice_RuntimeDDDStatus(1, remaining, total)` at `CellManager::PreFetchCells` @0x004558DE — a live "N of M cells" progress readout, cleared with `(0,0,0)` at @0x00455910 / @0x00455994. **#327 filed for this, correctly.** 2. If the block coincides with a teleport, the portal tunnel and the "In Portal Space - Please Wait..." notice — but, per **F2**, that notice is driven by the tunnel rotation segment (0.6–1.8 s, unconditional), not by the block, and acdream's 5 s arming threshold is not retail's trigger. The contract is right to say the 5 s threshold is not retail's; the commit message then contradicts it. ### Q3 — is AP-149 honest and correctly scoped? **Yes, and its retail chain is exactly right.** I verified every link: - `LScape::PreFetchCells` @0x00505660 walks the whole square and, per member, requires the terrain DBObj resident (`DBObj::Get` non-null, @0x0050575C); on in-file-but-not-loaded it kicks a prefetch of the type-2 LandBlockInfo record (`(esi & 0xfffffffe) | 0xfffe`, @0x0050579C) and reports not-ready. - `CLandBlock::PreFetchCells` @0x00530240 requires the LandBlockInfo record when `lbi_exists`. - `CLandBlockInfo::PreFetchCells` @0x0052E7C0 loops every building and every one of its portals into `CBldPortal::PreFetchCells` @0x0053BD00. And acdream's Far build is genuinely heightmap-only: `LandblockBuildFactory.BuildLocked` (`src/AcDream.App/Streaming/LandblockBuildFactory.cs:129-141`) early-outs for `LoadFar` with `Array.Empty()` and `PhysicsDatBundle.Empty`, skipping LandBlockInfo, scenery, buildings, and interior cells. The row's claim list is accurate, its risk column names the right symptom (distant buildings/scenery popping in after reveal), and its "do not let a later closeout claim parity" line is the right guard. **Is it larger than the row admits?** Two qualifications, neither fatal: - The row is scoped to the *outer* ring. Correct today. But it does not say that the boundary between "retail-complete" and "terrain-only" is `NearRadius`, which at High is 4 (768 m) against retail's uniform 8 (1536 m at default) — so acdream's fully-hydrated square is *smaller* than retail's entire prefetch square, not just its inner part. The stated ~768 m threshold in the row's Risk column captures this numerically; the framing ("outer ring") slightly understates that retail has no inner/outer distinction at all. - The row does not mention that the same terrain-only outer ring is what makes F1's demote hole reachable. That is a defect, not a divergence, so it belongs in ISSUES rather than the register — but AP-149 currently reads as if the outer arm works and is merely weaker. It does not work. ### Q4 — anything retail-visible broken or silently altered - **F1** — reveal can hang permanently. Retail-visible in the strongest sense. - **F2** — no behaviour change from this commit, but the wait cue is now shown in more situations and its arming remains non-retail. - `preferDestination` latching (F1 secondary) starves non-destination streaming to 25% for the session. - Indoor destinations are unchanged: `RequiredWindow` returns `(0,0)` (`WorldRevealReadinessBarrier.cs:206-217`), and `IsRenderNeighborhoodResident(cell, 0, 0)` reduces to the pre-#280 `IsNearTier && IsRenderReady` on the single member. Verified by inspection; retail's indoor arm is `CEnvCell::PreFetchCells` @0x0052D1E0 (the id-taking overload — address correct as cited). - Composite warmup staying `NearRadius`-scoped is sound: `LandblockBuildFactory` gives Far builds no entities at all, so widening it would walk 625 landblocks to warm nothing. - The `ACDREAM_PROBE_REVEAL_RADIUS=1` A/B is faithful: it forces `far = 1` and `near = clamp(NearRadius, 0, 1) = 1`, which is exactly the pre-#280 `IsNearTier && IsRenderReady` radius-1 gate. - The D6 scratch-set change (`PhysicsEngine.cs:48-52`, `:137-147`) is correct under the stated single-thread assumption; the method is a pure leaf and the set is engine-instance-owned, and the staging clone in `CollisionStagingBuilder` is a distinct `PhysicsEngine` with its own scratch. I did not find a concurrent caller. --- ## Bookkeeping audit | Item | Verdict | |---|---| | **AP-149** (new) | Present at register line 178. Retail chain verified end-to-end (all four addresses correct). Honest and correctly directional. Caveats in Q3. | | **AD-2** amendment | Factually correct on every retail claim I checked: `{3,5,8,11,15,25}` @0x007CA988, default 8, `SmartBox::SetRegion` @0x004531F0, `LScape::PreFetchCells` @0x00505660, `SmartBox::set_mid_radius` @0x00453180, `LScape::SetMidRadius` @0x00504C00. The sentence *"out to `FarRadius`, terrain publication only (`IsRenderReady`, which a `PublicationKind.Far` landblock satisfies through its empty spawn-adapter registration)"* is true as written and **false for the demote-produced Far tier** — this is the register's statement of F1's wrong assumption and must be corrected with the fix. | | **#326** (Viewing Distance option) | Correctly filed and well-scoped. One wrong address (F4). | | **#327** (DDD progress readout) | Correctly filed; `ECM_DDD::SendNotice_RuntimeDDDStatus` confirmed live at `CellManager::PreFetchCells` @0x004558DE. | | **#328** (5000 f far plane vs retail 4000) | Correctly filed; `Render::zfar = 4000.0f` byte-verified at VA 0x0081EC88 (`00 00 7a 45`). See F5 on the "never bounds" phrasing. | | **CLAUDE.md `ACDREAM_STREAM_RADIUS` rewrite** | Verified against source and correct on all four clauses: default unset (`QualityPreset.WithEnvOverrides` / `RuntimeOptions.LegacyStreamRadius`), forces `NearRadius` and only raises `FarRadius` (`SessionPlayerComposition.cs:249-257`), silently discarded by `RuntimeSettingsTargets.ApplyQuality` → `ReconfigureStreamingRadii` (`RuntimeSettingsTargets.cs:251-252`), and `ACDREAM_NEAR_RADIUS`/`ACDREAM_FAR_RADIUS` are the modern spelling (`QualityPreset.cs:45-46`). | | **`reference_two_tier_streaming.md` corrections** | Verified correct, including the load-bearing one: a Far publication *does* reach `LandblockPhysicsPublisher` (no `Kind != Far` guard in `LandblockPresentationPipeline.Advance`'s physics arm, `:600-660`) and terrain collision is published from the heightmap, which `PhysicsEngine.DemoteLandblockToTerrain` is explicitly written to preserve. The preset table, the Chebyshev note, and the retail `mid_radius` paragraph are all accurate. Its "`IsRenderReady` … a Far publication registers with `WantsLoaded = true` and an EMPTY desired mesh set, so it is render-ready" bullet inherits F1's error and needs the same correction. | | **Left unfiled** | (a) F1 — no issue exists for the demote hole. (b) F2 — the 5 s wait-cue arming has no divergence row; AD-2/AP-115 describe it as acdream behaviour without naming retail's actual trigger. (c) F3 — the mesh-build/GPU-upload strictness of the gate versus retail's DAT-residency predicate has no row. | --- ## Gates run - `dotnet build -c Release AcDream.slnx` → **0 errors**, 18 warnings (all pre-existing xUnit analyzer warnings). - `dotnet test -c Release AcDream.slnx --no-build` → 3 failures, **all three from a previous review session's deleted probe file** still present in the stale `AcDream.App.Tests.dll`. After `dotnet build -c Release tests/AcDream.App.Tests --no-incremental`: **App.Tests 4,157 passed / 0 failed / 3 skipped**. Other assemblies observed green in the same run: Runtime 1,217, Core 4,263 / 1 skipped, Core.Net 764, Headless 89, Bake 15. (My solution-wide invocation piped through `tail`, so I do not have reliable totals for Cli/Content/UI.Abstractions; nothing failed in the captured portion.) - Binary verification: `py tools/pdb-extract/check_exe_pdb.py "C:/Users/erikn/Downloads/acclient.exe"` → `=== MATCH ===`, GUID `9e847e2f-777c-4bd9-886c-22256bb87f32`, linker UTC 2013-09-06T00:17:56. Raw byte reads at VA 0x007CA988, 0x007991B0, 0x0081EC88, 0x007BD6A8 and code bytes 0x004D6FC0-0x004D7070 taken directly from the PE via section-mapped file offsets. ## What I checked and found clean So the PASS portions are auditable, these were examined and found correct: retail's six-value ladder and default; the one-square prefetch/loaded/drawn identity; the `>= 0x7F8` bounds test and acdream's matching skip; the blocking `SmartBox::UseTime` arm; the 5.0 s `CheckPrefetchStatus` constant and its comparison sense; the `Render::zfar` initialiser and its only two writers; the wait-cue string bytes; `LScape::SetMidRadius`'s `mid_width` formula and its `land_blocks` refusal; `SmartBox::set_mid_radius`'s save-reset-re-radius-re-arm order; `CellManager::Reset`'s conditional `release_all`; the full `CLandBlock` → `CLandBlockInfo` → `CBldPortal` prefetch chain; acdream's Far build contents; that a Far publication reaches the physics publisher and registers terrain collision; that terrain mesh upload precedes spawn-adapter registration so `IsRenderReady` is not a stamp for freshly-loaded Far tier; the indoor arm's unchanged behaviour; the composite-warmup scoping argument; the `ACDREAM_PROBE_REVEAL_RADIUS=1` A/B equivalence; the D6 scratch-set safety; the origin/destination coincidence that keeps the gate square inside the streaming square after a recentring teleport; and every documentation claim listed in the bookkeeping table.