Both #280 review lenses returned FAIL on the same defect, and both were
right. IsRenderNeighborhoodResident's widened outer arm requires
IsRenderReady out to FarRadius, justified by "a Far-tier landblock
registers with an empty mesh set and is therefore render-ready." That held
only for a landblock that ARRIVED as Far. The second, equally first-class
way to be Far tier is a Near->Far DEMOTE:
DemoteLandblock -> EnqueueNearLayerRetirement
-> LandblockRetirementStage.MeshReferences
-> GpuWorldState.ReleaseLandblockMeshReferences
-> LandblockSpawnAdapter.OnLandblockUnloaded => WantsLoaded = false
while DetachNearLayer deliberately keeps the landblock loaded, terrain-mesh
resident, terrain-collision resident and DRAWN. Nothing re-publishes an
already-loaded landblock, so the demoted member satisfied NEITHER arm of
the gate, permanently: wormhole tunnel plus centered "In Portal Space -
Please Wait..." forever, no recovery short of relog.
Reachable by ordinary play. Two consecutive recalls to the same landblock
with walking in between makes ChangesStreamingCenter false, so there is no
origin recenter and the region recentres through the ordinary demote diff.
Also reachable via a mid-hold quality-preset drop -- ironically the exact
scenario ReconcileDestinationReservationRadius was added to support. The
pre-#280 radius-1 gate never touched that band, because nothing inside the
Near ring can demote.
FIX SHAPE. Make the two routes genuinely equivalent rather than teaching
the predicate to tolerate the difference. ReleaseLandblockMeshReferences
becomes "reconcile the registration to the post-retirement tier": after the
release converges, if the landblock is still loaded AND still Far tier,
re-assert the empty registration -- the identical OnLandblockLoaded(lb,
empty) a PublicationKind.Far activation makes. It is empty by construction:
DetachNearLayer retains only live server projections, which the adapter's
atlas-tier filter skips. A full retirement is unaffected (DetachLandblock
clears both _loaded and _tierByLandblock), and a throwing release still
retries because the re-assert is only reached after the adapter converged.
The alternative -- "|| (IsFarTier && IsLoaded)" at the gate -- was
rejected: it fixes one caller while leaving IsRenderReady meaning two
different things, which is precisely how this defect arose. After this
change the predicate reads "drawable at its current tier" for every caller,
with no knowledge of how the landblock got there.
WHY THE TESTS MISSED IT, fixed here too:
- Proof obligation P2 was discharged against RESIDENCY (the FarRadius+2
eviction threshold) rather than against IsRenderReady, the gate's actual
atom. The contract now carries the correction and the restated
obligation: no transition may REVOKE IsRenderReady from a landblock that
stays inside FarRadius.
- WorldRevealDerivedWindowIntegrationTests advertised itself as end-to-end
against the real GpuWorldState but constructed it with no spawn adapter,
so its IsRenderReady degenerated to IsLoaded via the "?? true". The
single most load-bearing predicate in the change was stubbed out by a
null in the test named after it -- the same shape as C5b's D3 and #276's
three settler tests. Every fixture in that file now owns a real
LandblockSpawnAdapter.
- The P1 test's comment described its subject as "a Near-shaped completion
the streaming window has since DEMOTED to Far". It is not; it is a fresh
PublishAsFar, the case that does hold. Corrected, since a future reader
would have taken it as demote coverage.
Four new regression tests, all driving the real GpuWorldState +
LandblockSpawnAdapter + LandblockPresentationPipeline through an actual
demote, and all sabotage-verified in both directions (fail with the
production change reverted, pass with it):
NearToFarDemote_LeavesTheLandblockRenderReadyThroughTheRealPipeline
NearToFarDemote_LeavesTheLandblockRenderReadyUnderBudgetedRetirement
TieredWindow_StaysResidentAfterAnOuterRingDemote
OutdoorReveal_SurvivesAnOuterRingDemoteDuringTheHold
The budgeted variant exists because production composes
LandblockRetirementCoordinator.CreateBudgeted, whose MeshReferences stage
is a separate call site from the legacy pipeline's.
SECONDARY, same commit:
- R-1: ACDREAM_PROBE_REVEAL_RADIUS=0 was parser-accepted and
Runtime-rejected -- it yields far = 0 for an outdoor destination, which
fails invalid-readiness-shape on every acknowledgement, hanging the very
A/B route the probe exists to measure. Parser floor raised to 1, with a
7-case table test.
- R-2: the composite-warmup TRIGGER had silently moved onto the far
window's critical path. Pre-#280 the gate and the composite domain were
the same radius-1 square; #280 widened the gate without widening the
domain, so every composite upload serialised behind the last outer-ring
landblock for no readiness benefit. Warmup now starts once the NEAR
sub-window is published -- trigger scope == domain scope, as before. The
reveal gate is untouched: Evaluate still requires the full window AND
composite readiness.
- AP-150 filed: acdream's RetailWaitCueDelay = 5 s arming is NOT retail's
trigger, and #280's commit message got this wrong on both clauses. Retail
emits the notice unconditionally per tunnel rotation segment, in the else
arm of the segment-expiry test at 0x004D6FCD; segment duration is
RandDouble(0.6, 1.8) s, byte-decoded at 0x004D6FE6. The 5.0 constant at
VA 0x007991B0 is CellManager::CheckPrefetchStatus's prefetch RETRY
cadence and has nothing to do with the cue. acdream's own 0.6/1.8 segment
constants already match retail exactly; only the arming is wrong.
Adopting retail's unconditional emit is filed as #329 rather than folded
in here -- it is a user-visible presentation change and wants the user's
eyes.
- AP-151 filed: the gate is materially STRICTER than retail on the
mesh-build/GPU-upload axis. Retail's LScape::PreFetchCells blocks on DAT
RESIDENCY only -- no geometry construction, no upload; that work is lazy
at draw. acdream requires a DAT read, terrain mesh build, render-thread
upload, spatial commit, collision admission and spawn-adapter activation
per member of a 625-member window, metered at MaxCompletionsPerFrame.
Nothing bounds the hold. This is the OPPOSITE asymmetry from AP-149; both
are live at once, on different axes.
- AD-2's amendment stated the false Far-tier readiness assumption verbatim;
corrected, along with the same error in
claude-memory/reference_two_tier_streaming.md, which now carries an
explicit DO-NOT-RETRY on the special-case-the-predicate shape.
- AP-115 scope-noted (it covers the cue's presentation, not its arming).
- #326's SmartBox::set_mid_radius citation corrected: the entry is
0x00453180; 0x004531D0 is the mid-function re-arm branch.
Blast radius: GpuWorldState, LandblockSpawnAdapter,
WorldRevealReadinessBarrier and StreamingDiagnostics are all App-internal;
AcDream.Headless and AcDream.Runtime reference none of them outside
comments. Headless tests run green as part of the gate below, per C5b's
lesson about surveys that skip the no-window host.
Gates: Release build 0 errors, 18 pre-existing xUnit analyzer warnings.
Complete suite "dotnet test AcDream.slnx -c Release -m:1" with
ACDREAM_PAK_PATH set: 11,192 passed / 4 skipped / 0 failed, from a clean
rebuild (a prior session's deleted probe file had been compiled into a
stale test DLL). Baseline at fafc0b65 was 11,179 / 4 / 0; the +13 delta
reconciles exactly to this commit's additions -- 3 readiness tests, 1
integration test, 7 parser table cases, 2 warmup-trigger tests. None of the
known flakes #302/#308/#321 surfaced, and none is conflated with the
finding above.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
19 KiB
#280 architecture review — ownership, layering, blast radius, test quality
- Commit under review:
3aab05b0— fix(streaming): derive the portal reveal window from the live streaming radii (#280) - Branch / HEAD:
claude/acdream-physics-divergence-5aa784@fafc0b65 - Worktree:
.claude/worktrees/peaceful-visvesvaraya-e0a196 - Scope: ownership, layering, blast radius, test quality. Retail fidelity is a separate reviewer's.
- Mode: read-only. Production edits were made only as sabotage probes and
reverted; the tree is clean at
fafc0b65with no working-tree changes.
Verdict: FAIL
One confirmed defect, reproduced end-to-end through production code paths.
The predicate D2 widened from a 3×3 always-Near neighbourhood to the full Far
window now demands IsRenderReady from landblocks that the streaming system
permanently takes out of render-readiness while leaving them loaded. There
is no code path that restores them. Under two ordinary player flows — one of
which is the exact mid-hold radius change this commit added
ReconcileDestinationReservationRadius to support — the reveal gate becomes
unsatisfiable and the client stays in "In Portal Space - Please Wait…"
forever.
Everything else checked out. Layering held, the no-window host is genuinely untouched, the allocation fix is real, and the tests are unusually well disciplined about not re-encoding the constant under test. The failure is a single missed state transition, not sloppy work — but it is the failure mode the change's own proof obligation P2 was written to exclude, discharged against the wrong predicate.
Defects
D-1 — A demoted-but-loaded landblock is permanently not IsRenderReady, so the widened gate can never open (CONFIRMED, reproduced)
Severity: defect. Blocks the fix.
src/AcDream.App/Streaming/StreamingController.cs:270-278 — the new outer arm
requires _state.IsRenderReady(canonical) of every in-bounds member out to
farRadius.
IsRenderReady is not a property of "loaded":
src/AcDream.App/Streaming/GpuWorldState.cs:179-181IsRenderReady = _loaded.ContainsKey(id) && _wbSpawnAdapter.IsLandblockRenderReady(id)src/AcDream.App/Rendering/Wb/LandblockSpawnAdapter.cs:135-139returns false when the landblock has no registration, or a registration withWantsLoaded == false.
A Near→Far demote destroys exactly that registration while keeping the landblock loaded:
src/AcDream.App/Streaming/StreamingController.cs:555-559DemoteLandblock→LandblockPresentationPipeline.EnqueueNearLayerRetirementsrc/AcDream.App/Streaming/LandblockRetirementCoordinator.cs:309-312aNearLayerticket still runsCoreStages, which includesLandblockRetirementStage.MeshReferencessrc/AcDream.App/Streaming/LandblockRetirementCoordinator.cs:738-740→GpuWorldState.ReleaseLandblockMeshReferencessrc/AcDream.App/Streaming/GpuWorldState.cs:1559-1560→LandblockSpawnAdapter.OnLandblockUnloaded→WantsLoaded = false, registration droppedsrc/AcDream.App/Streaming/GpuWorldState.cs:1800-1835meanwhileDetachNearLayerkeeps_loaded[canonical]and flips_tierByLandblock[canonical] = Far
Nothing re-publishes an already-loaded landblock. RecenterTo
(StreamingRegion.cs:232-244) emits ToLoadFar only for ids absent from
_tierResidence, and a demoted block is still present as Far. Only a
promote back into the Near ring (StreamingRegion.cs:239-244 →
PublicationKind.PromoteExisting → ActivateLandblockPresentation →
OnLandblockLoaded) restores the registration. Members of the band
NearRadius+1 … FarRadius are never promoted, so they stay dead for the rest
of the window's life.
Evidence (temporary probes, run then removed):
| Probe | Result |
|---|---|
GpuWorldState.IsRenderReady after LandblockPresentationPipeline.BeginNearLayerRetirement (the production demote entry point) |
false |
GpuWorldState.IsRenderReady after GpuWorldState.RemoveEntitiesFromLandblock (same DetachNearLayer + ReleaseLandblockMeshReferences pair) |
false |
Full Near 5×5 window; controller.IsRenderNeighborhoodResident(dest, 1, 2) before one outer-ring demote |
true |
| …the same call after that single demote | false, and never recovers |
All three probes used a real GpuWorldState + real LandblockSpawnAdapter +
real LandblockPresentationPipeline.
Reachable failure scenarios
-
Mid-hold quality change (the one this commit explicitly added code for).
StreamingController.ReconfigureRadiiat:484-488emitsDemoteLandblockfor every currently-Near landblock that the new preset puts in the Far band. Presets are High(4,12)→ Low(2,5)(src/AcDream.UI.Abstractions/Settings/QualityPreset.cs:31-34), so a High→Low switch demotes the Chebyshev 3–5 band, which is inside the new Far window. The gate then requiresIsRenderReadyfrom all of them. Result: the hold never ends, andWorldRevealCoordinator.ReconcileDestinationReservationRadius(:521-543) cheerfully re-opens a reservation on a square that can no longer converge. Note also thatNearRadius/FarRadiusonly publish at transaction convergence (StreamingController.cs:886-888) while the demote mutations run earlier (:860-876), so for several frames the barrier measures the OLD wide window against blocks already demoted for the NEW narrow one. -
A reveal that does not recenter the origin.
LocalPlayerTeleportController.cs:934only calls_streaming.BeginRecenterwhentransition.ChangesStreamingCenter, i.e. when the destination landblock differs from the world-origin landblock. Origin recenter is the only thing that clears the window (GpuWorldState.cs:1481_loaded.Clear()). Walking ≳NearRadius + 2landblocks from the last origin (StreamingRegion.cs:263-270) demotes blocks into the Far band; a subsequent teleport back to the origin landblock (lifestone recall → travel → lifestone recall) takes the no-recenter path and inherits them. Blocks that land back inside the Near ring are promoted and recover; blocks innear+1 … fardo not.
Why the old gate was safe: at OutdoorNeighborhoodRadius = 1 the required
set was the destination's own 3×3, which during a hold is pinned to the
streaming centre (StreamingFrameController.SelectObserver:194-200 freezes the
observer at _origin while PlayerState.PortalSpace) and therefore always
Near tier. Demotion inside the Near ring cannot happen. The defect is
introduced by this commit, not exposed by it.
Why P2 missed it. docs/research/2026-08-05-280-contract.md:876-884 states
P2 as "an outer-ring member cannot be evicted while it is inside
FarRadius" and cites the FarRadius + 2 unload threshold. That is true and
irrelevant: the gate's atom is IsRenderReady, not residency. P2 was
discharged against the wrong predicate. Every remaining sentence of the
static proof (hysteresis, recenter, dungeon collapse) is correct.
Also note: the P1 test's own comment
(tests/AcDream.App.Tests/Streaming/StreamingControllerReadinessTests.cs:567-570)
describes its subject as "A Near-shaped completion that the streaming window
has since demoted to Far". It is not — it is a fresh PublishAsFar of a
landblock that was never loaded, which is the case that does hold. The
comment names the one case that is false. A future reader will take it as
coverage of the demote path.
Latent risks
R-1 — ACDREAM_PROBE_REVEAL_RADIUS=0 is accepted by the parser and rejected by Runtime
src/AcDream.App/Streaming/StreamingDiagnostics.cs:54-55 accepts any
value >= 0. ApplyRevealRadiusOverride then yields
StreamingRevealWindow(0, 0), so RequiredWindow returns far = 0 for an
outdoor destination, and
src/AcDream.Runtime/World/RuntimeWorldTransitState.cs:585-597 fails
invalid-readiness-shape on every acknowledgement (outdoor ⇒ >= 1). The
A/B probe would hang the route it is meant to measure. The documented value is
1; the parser should refuse 0 rather than let the two halves of the same
commit disagree. One line.
R-2 — the composite-warmup trigger moved onto the far window's critical path, undocumented
WorldRevealReadinessBarrier.Prepare:120-133 starts composite preparation only
once _isRenderNeighborhoodReady(dest, near, far) is true. Before this commit
that predicate was the destination's 3×3; it is now the entire 25×25. The
commit message and D3 justify keeping the composite radius at NearRadius
(correct — Far builds carry no entities), but say nothing about the trigger.
Net effect: composite upload can no longer overlap far-ring streaming; the
hold is longer than the streaming work alone requires by the whole warmup
duration. This is a real serialisation the "expect longer holds" paragraph
does not account for, and it is separable — warming composites at Near-ready
would restore the overlap without weakening the gate.
R-3 — two different windows describe "destination publication incomplete"
StreamingController.cs:719-724 computes the destination-lane preference with
(Math.Min(NearRadius, DestinationRadius), DestinationRadius) — live
NearRadius against the reservation's radius — while the gate uses
RequiredWindow's (clamp(NearRadius, 0, FarRadius), FarRadius). Between a
preset change and reservation reconciliation these name different squares, so
the work-lane prioritisation can consider the destination complete on a
square the gate still rejects. Cosmetic today; it becomes a real starvation
question once R-1/D-1 are fixed and holds get long.
R-4 — Runtime lost its only cross-host consistency check
The indoor ? 0 : 1 equality became indoor ⇒ 0, outdoor ⇒ ≥ 1
(RuntimeWorldTransitState.cs:585-597). The layering argument is right —
Runtime cannot learn the graphical host's streaming configuration, and
plumbing App radii in would be exactly the C5b failure. The cost is that
Runtime can no longer detect an App regression to a hardcoded radius: a
future revert of D1 would emit 1 and pass. Accepted, but worth recording
that the invariant is now shape-only and the only remaining guard on the
value is the App-side tests.
What was checked and is sound
Layering / ownership (attack 6) — held.
WorldRevealCoordinator and WorldRevealReadinessBarrier are internal to
AcDream.App. src/AcDream.Headless/AcDream.Headless.csproj references only
AcDream.Runtime. Both non-graphical producers
(HeadlessSessionWorldProjection.cs:979,
RuntimeLiveEntitySessionController.cs:782) keep emitting their own
centre-ring token and are legal under the loosened shape by construction, and
both were annotated in place explaining why they must not track the
graphical radius. No App type crossed into Runtime. AcDream.Headless.Tests
passes 89/89 including its dependency/loaded-assembly guards. C5b's lesson was
applied, not repeated.
P1 (attack 2) — independently verified TRUE, by reading rather than by
reading the test. PublishAsFar (LandblockPresentationPipeline.cs:432-522)
constructs a LoadedLandblock with Array.Empty<WorldEntity>() and
PhysicsDatBundle.Empty; the spatial commit
(LandblockPresentationPipeline.cs:900-916) routes PublicationKind.Far
through CommitLandblockSpatial, which returns a publication with
RequiresActivation defaulted true (GpuWorldState.cs:24), so
ActivateLandblockPresentation (:1009-1035) calls OnLandblockLoaded with
an empty entity list and empty AdditionalRenderIds. The registration is
created with WantsLoaded = true and both reference dictionaries empty, so
IsLandblockRenderReady returns true with no IWbMeshAdapter upload. The one
early-out (GpuWorldState.cs:913-923, a stale Far completion over a live Near
tier) returns RequiresActivation: false, but that case is already
registered. P1 holds. The fix shape does not collapse. Its failure is D-1,
a different transition.
Same check for the collision arm, which is equally load-bearing and was not
called out as a proof obligation: LandblockPhysicsPublisher.AdvanceBeginOne
(:390-435) builds the terrain surface from the heightmap with no dat bundle
and stages it, so a Far publication does register terrain collision;
AdvanceDemotion (:675-683) calls DemoteCollisionToTerrain, which
retains terrain. IsNeighborhoodTerrainResident therefore stays satisfiable
across a demote. The render arm is the only one that breaks.
Reveal-hang sweep (attack 1) — all other configurations converge.
| Configuration | Result |
|---|---|
| Map edge (coords clamped 0/254) | Safe. Gate skips > 254 (StreamingController.cs:265-266); StreamingRegion loads up to 0xFF (:80). The loaded set is a strict superset of the required set, and PhysicsEngine.cs:157 uses the identical bound. |
| Dungeon → outdoor | Safe. TryCommitOriginRecenterCore:1235-1251 clears _collapsed for a non-dungeon destination and nulls _region so the next tick bootstraps the full window. |
| Indoor destination | Safe. RequiredWindow returns (0,0) before touching the live window (WorldRevealReadinessBarrier.cs:206-215), so no streaming state can gate it. |
| Recenter in flight | Safe. Tick is blocked while _originRecenterRetirement is open, radii requests defer, and _loaded.Clear() (GpuWorldState.cs:1481) guarantees every member of the rebuilt window gets a fresh publication and registration. |
| Destination/observer landblock offset | Safe during a hold: StreamingFrameController.SelectObserver:194-200 pins the observer to _origin while PlayerState.PortalSpace, so the gate's inner ring and the streaming Near ring are the same square. Worth noting the new inner arm has zero margin here where the old one had NearRadius − 1; the pin is now load-bearing. |
FarRadius == 0 on an outdoor destination |
Not reachable from presets (min far = 5); reachable only via R-1. |
D6 allocation fix — real, and the sabotage number is honest.
PhysicsEngine.cs:49-52,146-149. Single production call site
(SessionPlayerComposition.cs:384), main thread, leaf method — the
instance-owned scratch is safe, and it is cleared at entry so no stale state
can leak across sessions.
Test discipline — good, with two gaps. No test re-encodes the constant
under test: every radius assertion references the fake window's own input
(WorldRevealReadinessBarrierTests, WorldRevealDerivedWindowIntegrationTests).
No new Skip=. No test deleted — one was renamed
(OutdoorReveal_JoinsNearRenderTexturesAndTerrain →
RequiredWindow_IsRereadOnEveryEvaluationWithoutReconstruction) with its
original assertions carried into a new
OutdoorReveal_JoinsRenderTexturesAndTerrainOverTheDerivedWindow; coverage is
preserved and the +36 arithmetic holds.
Gaps:
- No test exercises a demote. That is D-1.
WorldRevealDerivedWindowIntegrationTestsadvertises itself as end-to-end against "the REALStreamingController,GpuWorldState, andPhysicsEngine", but constructsnew GpuWorldState()with no spawn adapter, soIsRenderReadydegenerates toIsLoadedvia the?? trueatGpuWorldState.cs:181. The single most load-bearing predicate in the whole change is stubbed out by a null in the test named after it. It also builds its ownrevealWindowlambda rather than the production one inSessionPlayerComposition.cs:373-379, so theApplyRevealRadiusOverridewrapper is never exercised in composition.
Sabotage spot-checks — 4 of 9 families reproduced (asked for ≥ 3).
| Sabotage | Named test(s) | Result |
|---|---|---|
PhysicsEngine: scratch → new HashSet<uint>() |
WarmedNeighborhoodQuery_AtTheFarRadius_AllocatesNothing |
FAIL, "allocated 27,712,000 bytes" — S5's claimed figure reproduces to the byte |
StreamingController: revert tier split to !IsNearTier || !IsRenderReady |
TieredWindow_OuterRingFarTierMemberIsResident, TieredWindow_AbsentOuterRingMemberIsNotResident, TieredWindow_MapCornerDestinationConvergesAtAWideRadius, OutdoorReveal_HoldsUntilTheWholeDerivedWindowIsPublished ×2, LoginReveal_UsesTheSameWidenedGateAsPortalArrival |
6 FAIL |
RuntimeWorldTransitState: shape → == (isIndoor ? 0 : 1) |
OutdoorReadinessShape_AcceptsAnyDerivedStreamingRadius |
6 FAIL (all inline radii) |
WorldRevealCoordinator: neuter ReconcileDestinationReservationRadius |
MidHoldRadiusChange_ReopensTheReservationOnTheSameGeneration |
1 FAIL |
All four reverted; git status clean afterwards. No sign of the C5b-D3 class
(a test that passes with its own change reverted) in the families checked.
Suite at HEAD fafc0b65, Release, verified by running it:
11,179 passed / 4 skipped / 0 failed — exactly the expected
11,178 + 1 Core settler test. Per project: UI 546, Content 124, Runtime 1217,
Cli 4, Bake 15, App 4157/3 skips, Headless 89, Core.Net 764, Core 4263/1 skip.
None of #302/#308/#321 surfaced in this run and none are conflated with the
finding above.
Process rules — clean. No suppression flag, grace period, retry loop, or
symptom guard was introduced. ACDREAM_PROBE_REVEAL_RADIUS lives in a
diagnostic owner per Code Structure Rule 5 and is correctly argued as a
measurement probe rather than a shipped knob. AD-2 was amended and AP-149
filed in the same commit, satisfying the divergence-register rule. The
ACDREAM_STREAM_RADIUS CLAUDE.md correction is accurate against
SessionPlayerComposition.ComposeCore:249-257.
Recommended disposition
Do not ship the gate widening until D-1 is closed. The two shapes worth considering, in preference order:
- Make a demote re-register the Far tier. The demote already leaves a
fully valid Far-tier landblock behind; the registration it drops is the
Near mesh set.
LandblockRetirementStage.MeshReferenceson aNearLayerticket should release the Near references and then re-assert an empty Far registration, exactly asPublishAsFardoes — i.e. the sameOnLandblockLoaded(landblock, empty)call the Far publication makes. This makesIsRenderReadymean "drawable at its current tier", which is what both the render path and the gate already assume it means. - Re-publish demoted members inside a destination reservation. Weaker:
it fixes the gate without fixing the predicate, and leaves the next caller
of
IsRenderReadyholding the same trap.
Whichever is chosen, it needs a test at the demote transition —
Near publish → demote → IsRenderNeighborhoodResident(dest, near, far) — and
the WorldRevealDerivedWindowIntegrationTests fixture should be given a real
LandblockSpawnAdapter so its IsRenderReady stops being a tautology.
R-1 and R-2 are one-line and one-decision respectively and can ride along.