acdream/docs/research/2026-06-11-holistic-map/wf2-sky-weather-scenery.md
Erik 5e2f99d08e docs: Phase A comparison + Phase B port plan (holistic building-render investigation)
Deliverable 1: docs/research/2026-06-11-building-render-acdream-vs-retail-
comparison.md - the acdream-vs-retail architecture comparison synthesized
from two ultracode mapping fan-outs (11/12 areas, ~90 agents, every retail
claim Ghidra/pc-cited, every acdream claim file:line, 40/76 divergences
adversarially verified so far; raw per-area evidence committed under
docs/research/2026-06-11-holistic-map/).

Headline findings: (1) retail flattens GfxObjs/cells at load exactly like
us (ConstructMesh + RemoveNonPortalNodes) - the MDI pipeline survives;
(2) the phantom/door mechanism is the skipNoTexture draw-time surface gate
(dat-confirmed); (3) retail never geometrically clips world geometry -
aperture exactness is a DEPTH discipline (punch maxZ1 / seal maxZ2 / gated
clear + far-to-near whole-mesh draws) - reframes #114; (4) flood admission
is already faithful, the trigger/depth/multi-view/cone-culling layers are
missing; (5) #115 root cause verified (boom damping severed from the
published collided viewer); collision A6.P4 design verified with
corrections (signed other_portal_id >= 0 gate).

Deliverable 2: docs/plans/2026-06-11-building-render-port-plan.md - the
phased port plan (BR-1 surface gate, BR-2 depth punch/seal, BR-3 delete
the shell chop, BR-4 draw-driven floods, BR-5 viewconeCheck, BR-6 one
gate, BR-7 collision A6.P4, BR-8 camera/lighting/LOD) with per-phase
acceptance criteria, bug closures, keep-list, and a playable-after-every-
phase migration order. AWAITING USER APPROVAL - no implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 05:54:12 +02:00

81 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 2.3 — Sky, weather, and procedural scenery vs the portal-view discipline
## RETAIL
FRAME ENTRY — SmartBox::RenderNormalMode (Ghidra 0x453aa0). The branch key is the VIEWER (collided camera) cell: `(viewer.objcell_id & 0xffff) < 0x100` = viewer outdoors. Outdoors: LScape::update_viewpoint(viewer cell) → Render::set_default_view (no portal clip list) → useSunlightSet(1) → LScape::draw. Indoors: if viewer_cell->seen_outside, LScape::update_viewpoint(Position::get_outside_cell_id(viewer)) — this only re-centers the 2D landblock draw list under the indoor viewer, it draws nothing — then RenderDevice vtable+0x48 = RenderDeviceD3D::DrawInside (vtable map pc:1037065; impl 0x0059f0d0) which tail-calls PView::DrawInside(indoor_pview, viewer_cell).
SKY/WEATHER DRAW SITE — LScape::draw (Ghidra 0x00506330): (1) GameSky::Draw(sky, 0) FIRST, (2) back-to-front DrawBlock per landblock with in_view != OUTSIDE, (3) if LScape::weather_enabled: GameSky::Draw(sky, 1) LAST. GameSky::Draw (Ghidra 0x00506ff0) gates its whole body on `is_player_outside() || pass==0`: the sky pass is unconditional (whenever LScape::draw runs at all), the weather pass additionally requires the PLAYER to be outdoors. SmartBox::is_player_outside (Ghidra 0x00451e80) = (player cell & 0xffff) < 0x100. Depth state for BOTH passes: SetDepthBufferMode(DEPTHTEST_ALWAYS, z-write OFF), zfar ×4, fog forced per LScape::m_override_enabled; restored to (LESSEQUAL, write on) after (0x00507063/0x005070fc). Sky pass iterates sky_obj skipping property bit 0x01 (after-cell members), bit 0x04 objects when !weather_enabled, bit 0x02 under the admin fog override, calling CPhysicsObj::DrawRecursive each (pc:268704-268760). Weather pass = RenderDevice::DrawObjCellForDummies(after_sky_cell) (0x005070da; impl 0x005a0760 = UpdateObjCell + shadow-part sort + DrawObjCell). GameSky owns two dummy CEnvCells, before_sky_cell/after_sky_cell (acclient.h:35426): MakeObject (0x00506ee0) puts props&1 objects in after_sky_cell, the rest in before_sky_cell, and refuses to create props&4 (weather) objects while weather is disabled (also created/deleted on toggle by CreateDeletePhysicsObjects 0x005073c0, pc:268912-269036).
SKY POSITION SmartBox::set_viewer (0x00452c40) calls LScape::set_sky_position(this->lscape, &this->viewer) (pc:91830 / 0x00452d45; impl 0x00504c30) → GameSky::UpdatePosition (0x00506dd0, BN pc:268569-268618): both dummy cells snap to the VIEWER's cell id + frame; weather objects (bit 0x04) snap x,y to the viewer origin and, when bit 0x08 is clear, pin z := 120.0f (constant 0xc2f00000 stored at 0x00506e96-e98 — absolute world height, not camera-relative). So the rain cylinders (GfxObj 0x01004C42/0x01004C44, ~815 m tall) ride the camera in x,y but hang at fixed world z 120.
INDOOR LOOKING OUT — PView::DrawInside (0x005a5860, pc:433793) → ConstructView (0x005a57b0: zero outside_view.view_count, BFS flood via InitCell/ClipPortals/AddViewToPortals) → PView::DrawCells (0x005a4840, pc:432709). ClipPortals (0x005a5520): a portal whose other_cell_id == 0xFFFFFFFF is the OUTSIDE; if this->draw_landscape, its accumulated clip view is merged into the pview's outside_view via Render::copy_view (clipped when global `cliplandscape` != 0, unclipped otherwise; 0x005a566c-5711). PView::DrawCells then runs FOUR stages: (a) if outside_view.view_count > 0 → useSunlightSet(1), Render::PortalList = pview, **LScape::draw(lscape)** — the ENTIRE outdoor world draws through the doorway: sky pass, terrain blocks, buildings, scenery, outdoor statics/creatures, and the weather pass which self-gates on is_player_outside (player indoors → no rain even through the door). Every mesh in this slice is portal-clipped: DrawMesh (0x005a0860) loops PortalList views, viewconeCheck per view, and draws per intersecting view under that view's clip planes. (b) Clear(4 /*depth*/) — a FULL depth-buffer clear, conditional on portalsDrawnCount/forceClear (0x005a48a9) — then per cell (reverse cell_draw_list) per view, the OUTSIDE portal polys (other_cell == 1) are re-drawn via D3DPolyRender::DrawPortalPolyInternal (0x005a49af-49b7): a "z-stamp" that writes the doorway aperture's true depth back so indoor geometry lying beyond the doorway can't paint over the outside image. (c) useSunlightSet(0) + restore_all_lighting; shells per cell per view via DrawEnvCell (0x0059f170; planeMask=0xffffffff submit pc:427922). (d) per cell: Render::PortalList = the cell's last portal_view; DrawObjCellForDummies(cell) — cell contents portal-clipped.
OUTDOOR CONTENT (what the through-door slice contains) — RenderDeviceD3D::DrawBlock (0x005a17c0): per LandCell, DrawLandCell (0x0059f120 — terrain polys) then DrawSortCell (0x0059f140): if the cell has a building → DrawBuilding, then DrawObjCell (the cell's object list). DrawBuilding (0x0059f2a0, pc:429282-429295) installs outdoor_pview->outdoor_portal_list = building->portals before CPhysicsPart::Draw; portal polys inside the building's DrawingBSP dispatch RenderDeviceD3D::DrawPortal (0x0059f0e0) → PView::DrawPortal (0x005a5ab0, pc:433895) → ConstructView(CBldPortal) (0x005a59a0) → DrawCells of that building's interior. I.e. NESTED recursion: standing inside looking out, another building's interior renders through its open door/window because DrawBuilding runs inside the through-door LScape::draw.
SCENERY — CLandBlock::get_land_scenes (0x00530460, pc:314322): pseudo-random ObjectDesc::Place per scene-type slot; skips cells holding buildings (CSortCell::has_building 0x00530865), road cells (on_road 0x005307ce), too-steep terrain (CheckSlope 0x0053089a); then CPhysicsObj::makeObject → set_initial_frame → **add_obj_to_cell(landcell)** (0x00530923) + CLandBlock::add_static_object. Scenery is therefore an ordinary per-LandCell object — drawn through DrawSortCell/DrawObjCell and portal-clipped through doorways exactly like every other static. There is no separate scenery draw path.
## ACDREAM
ROOT PICK — GameWindow.OnRender: `clipRoot = viewerRoot ?? _outdoorNode` (src/AcDream.App/Rendering/GameWindow.cs:7497). Steady-state frames (eye indoors OR outdoors) ALL go through RetailPViewRenderer.DrawInside; the `clipRoot is null` "Outdoor LScape entry" (GameWindow.cs:7546-7587 sky+terrain, 7874-7889 weather) survives only as the pre-spawn/login safety path. Sky policy gate: `renderSky = viewerRoot is null || rootSeenOutside` (GameWindow.cs:7423).
DRAWINSIDE — src/AcDream.App/Rendering/RetailPViewRenderer.cs:44-109: PortalVisibilityBuilder.Build floods from the root; exit portals (OtherCellId==0xFFFF) union their clipped screen regions into OutsideView (src/AcDream.App/Rendering/PortalVisibilityBuilder.cs:279, 87); the synthetic outdoor node seeds a FULL-SCREEN OutsideView quad (PortalVisibilityBuilder.cs:80-89). Outdoor-node roots additionally merge per-building interior floods within 48 m (RetailPViewRenderer.cs:60-61, 115-145) — interior roots do NOT. ClipFrameAssembler produces ≤8-plane slices + an NDC AABB each; InteriorEntityPartition (src/AcDream.App/Rendering/InteriorEntityPartition.cs:22-79) buckets every landblock entity: indoor ParentCellId→ByCell, outdoor/none→Outdoor, server-guid-without-cell→LiveDynamic.
OUTSIDE SLICE — DrawLandscapeThroughOutsideView (RetailPViewRenderer.cs:214-238) loops slices: SetTerrainClip(slice.Planes) + entity clip routing, then GameWindow.DrawRetailPViewLandscapeSlice (GameWindow.cs:9465-9551): scissor to the slice NDC AABB → SkyRenderer.RenderSky (9486, clip distances ON) → SkyPreScene particles → terrain Draw (9496) → the WHOLE Outdoor bucket (scenery + dat stabs + building exteriors + outdoor-parented live entities) via WbDrawDispatcher, frustum cull + slice clip routing (9503-9512) → outdoor-filtered Scene particles with clip distances DISABLED, scissor only (9518-9530) → SkyRenderer.RenderWeather (9535) + SkyPostScene particles, gated ONLY on renderSky. After all slices: a SCISSORED depth clear per slice, interior roots only (GameWindow.cs:7644-7652; explicitly null for outdoor-node roots); then DrawExitPortalMasks — declared on the context (RetailPViewRenderer.cs:534) but NEVER assigned by GameWindow, so the pass no-ops (RetailPViewRenderer.cs:331-332); then DrawEnvCellShells (GL-clipped only for outdoor roots per #114 scope, :104-105, 378-398); then DrawCellObjectLists + per-cell particles (scissor only, clip off; GameWindow.cs:9553-9580).
SKYRENDERER — src/AcDream.App/Rendering/Sky/SkyRenderer.cs (WB SkyboxRenderManager port): RenderSky draws the pre-scene partition (Properties bit 0x01 clear), RenderWeather the post-scene partition (bit set) (:106-144, 219-235 — correct retail bit semantics, citing MakeObject 0x00506ee0). GL state: depth test OFF + depth mask OFF + cull off + per-submesh additive/alpha blend (:194-210) — equivalent to retail's DEPTHTEST_ALWAYS/no-write. View translation zeroed = camera-centred sky (:175-178). Weather 120 offset applied as a CAMERA-RELATIVE model translation for bit4 && !bit8 objects (:307-308). Fog-override bit 0x02 skip implemented (:240-241). sky.vert writes gl_ClipDistance from the terrain-clip UBO (src/AcDream.App/Rendering/Shaders/sky.vert:153) so the doorway slices genuinely clip sky and rain.
SCENERY — SceneryGenerator.Generate (src/AcDream.Core/World/SceneryGenerator.cs:86 via WbSceneryAdapter) → GameWindow.BuildSceneryEntitiesForStreaming (GameWindow.cs:5290-5473): scenery becomes WorldEntity ids 0x80XXYYII with MeshRefs and NO ParentCellId (5463-5472) → lands in the Outdoor partition bucket → drawn once per outside slice with per-entity frustum cull + slice clip planes. Building suppression at generation uses a 9×9 vertex-grid set derived from building origins (5310-5316). There are no per-LandCell object lists outdoors — the bucket is flat.
## DIVERGENCES
### [CRITICAL] outside-portal-zstamp-missing (UNVERIFIED (verifier hit token limit)) — No depth re-stamp of outside portal polys after the outside-view draw (and the depth clear is scissored, not full)
- blastRadius: #108 (grass texture sweeping across the upstairs door opening during the cellar ascent — the outside image / terrain drawn through the doorway region is never re-fenced in depth) and #109 (far exit door oscillating between door texture and background — a per-frame depth race in the doorway rectangle between the slice's cleared depth, the door entity, and shell geometry). Both are top entries in the 2026-06-11 holistic mandate.
- retailEvidence: PView::DrawCells (0x005a4840, pc:432709): after LScape::draw through outside_view it issues Clear(4 /*depth, FULL buffer*/) conditional on portalsDrawnCount (0x005a48a9), then per cell per view re-draws every OUTSIDE portal poly (other_cell == 0xFFFFFFFF) via D3DPolyRender::DrawPortalPolyInternal (0x005a49a0-49b7) — writing the doorway aperture's real depth back so indoor geometry beyond the doorway cannot overpaint the outside image, while geometry in front of it still occludes normally.
- acdreamEvidence: Depth clear is scissored to each slice's NDC AABB and only for interior roots (GameWindow.cs:7644-7652). The z-stamp stage exists as RetailPViewRenderer.DrawExitPortalMasks (RetailPViewRenderer.cs:325-343) but the callback is null — GameWindow's RetailPViewDrawContext initializer (GameWindow.cs:7604-7663) never assigns DrawExitPortalMasks, so the pass no-ops every frame.
- portShape: Wire the masks stage: after the slice depth-clears, draw each visible cell's exit-portal polygons depth-only (color mask off) per slice — the portal quads already exist as PortalRef polys per the e223325 dat finding. Match retail's full-buffer Clear(depth) gated on any-slice-drawn rather than per-slice scissored clears (or prove the scissored equivalent identical and document it). Order: outside slices → depth clear → portal z-stamp → shells → objects, exactly DrawCells.
### [HIGH] weather-indoor-gate (UNVERIFIED (verifier hit token limit)) — Weather pass not gated on is_player_outside — rain draws through doorways while the player is inside
- blastRadius: Rain/snow visibly composites in the doorway slice (depth-test off) when standing inside a building looking out; retail shows zero weather in that situation. Part of the 'indoor world feels right' gap; no dedicated issue number yet.
- retailEvidence: GameSky::Draw (0x00506ff0) gates its body on `is_player_outside() || pass==0` (0x00507009) — the weather pass (pass 1, DrawObjCellForDummies(after_sky_cell) at 0x005070da) requires the PLAYER cell to be outdoor (SmartBox::is_player_outside 0x00451e80: (cell & 0xffff) < 0x100), independently of the viewer/outside_view. LScape::draw additionally requires LScape::weather_enabled (0x0050638b-96).
- acdreamEvidence: DrawRetailPViewLandscapeSlice calls _skyRenderer.RenderWeather inside every OutsideView slice gated only on renderSky (GameWindow.cs:9533-9536), and renderSky is the viewer-root seen_outside policy (GameWindow.cs:7423) the player's indoor/outdoor state is never consulted.
- portShape: Pass a playerIsOutside bool (player CellId & 0xFFFF < 0x100 already computed for lighting at GameWindow.cs:7291-7320) into the slice context; skip RenderWeather + the SkyPostScene weather particles when false. RenderSky stays ungated (retail pass-0 is unconditional).
### [HIGH] particles-not-portal-clipped (UNVERIFIED (verifier hit token limit)) — Particles draw with clip distances disabled — scissor rectangle only, no portal-plane clipping
- blastRadius: The reported particles-through-walls bug: an emitter whose particles fall inside the doorway's bounding RECTANGLE but outside the portal WEDGE paints across interior walls; same for per-cell particles bleeding across neighbor cells inside the AABB.
- retailEvidence: Retail particles are cell objects drawn through DrawObjCellForDummies under the active Render::PortalList DrawMesh (0x005a0860) loops the portal views and draws only per intersecting view with that view's clip planes installed (Render::set_view), i.e. polygon-level portal clipping identical to statics.
- acdreamEvidence: Both particle draw sites explicitly DisableClipDistances() before drawing and rely on the slice/cell NDC-AABB scissor alone: outdoor slice particles at GameWindow.cs:9518-9530, per-cell particles at GameWindow.cs:9568-9579 (DrawRetailPViewCellParticles).
- portShape: Give the particle pipeline the same slice clip the sky already has: write gl_ClipDistance in the particle vertex/billboard shader from the slice planes (the terrain-clip UBO is already bound), enable clip distances around the draw, keep the scissor as a cheap pre-cull.
### [MEDIUM] no-nested-building-flood-through-outside-view (UNVERIFIED (verifier hit token limit)) — Interior roots never flood neighbor buildings — their interiors are absent from the through-door outside view
- blastRadius: Standing inside looking out a doorway at another building with an open door or window: retail renders that building's interior through its aperture; acdream shows the aperture as background/unsealed (same artifact family the outdoor look-in had pre-R-A2). Contributes to 'indoor world feels right'; not yet a numbered issue.
- retailEvidence: The through-door slice is the full LScape::draw, whose DrawSortCell (0x0059f140) calls DrawBuilding (0x0059f2a0, pc:429282-429295); DrawBuilding installs outdoor_pview->outdoor_portal_list and the building DrawingBSP's portal polys dispatch RenderDeviceD3D::DrawPortal (0x0059f0e0) → PView::DrawPortal (0x005a5ab0, pc:433895) → ConstructView(CBldPortal) (0x005a59a0) → DrawCells of the neighbor interior — nested recursion reachable from inside-looking-out.
- acdreamEvidence: MergeNearbyBuildingFloods runs only when ctx.RootCell.IsOutdoorNode (RetailPViewRenderer.cs:60-61); for interior roots NearbyBuildingCells is null by construction (GameWindow.cs:7610). The Outdoor bucket draws neighbor buildings' EXTERIOR entities through the slice (GameWindow.cs:9503-9512) but no interior cells flood.
- portShape: When an interior root has OutsideView slices, run the existing BuildFromExterior per-building seeding (the R-A2 machinery, keep-listed) for buildings whose entrance portals intersect a slice, intersect the resulting views with the slice planes, and append to the frame — the renderer already merges per-building frames (MergeBuildingFrame, RetailPViewRenderer.cs:151-160).
### [LOW] outdoor-objects-flat-bucket (UNVERIFIED (verifier hit token limit)) — Outdoor scenery/statics drawn as one flat bucket per slice instead of per-LandCell object lists
- blastRadius: No single named bug; costs are structural: the full outdoor entity set is re-dispatched once per doorway slice (perf when multiple doorways visible), alpha-blended outdoor objects have no near-to-far cell ordering, and per-cell semantics retail relies on (cell in_view, building-cell suppression at draw time) have no home. Mostly masked today by clip planes + scissor + depth buffer.
- retailEvidence: Scenery is placed INTO land cells (CPhysicsObj::add_obj_to_cell, 0x00530923 in CLandBlock::get_land_scenes 0x00530460) and drawn per cell in block draw order: DrawBlock (0x005a17c0) → per-LandCell DrawLandCell (0x0059f120) + DrawSortCell (0x0059f140) → DrawObjCell, with per-view viewcone checks in DrawMesh (0x005a0860).
- acdreamEvidence: Scenery entities carry no ParentCellId (GameWindow.cs:5463-5472) → InteriorEntityPartition.Outdoor (InteriorEntityPartition.cs:42-49, 61-64); the whole bucket is drawn in one WbDrawDispatcher call per OutsideView slice with frustum cull only (GameWindow.cs:9503-9512).
- portShape: Long-term: per-LandCell object lists (the render twin of the A6.P4 per-cell shadow architecture), letting the slice walk cells in draw order like DrawBlock. Near-term acceptable as-is; do not re-fix under this area alone.
### [LOW] rain-anchor-z-relative (UNVERIFIED (verifier hit token limit)) — Rain cylinder z pinned camera-relative (120 below camera) instead of world-absolute z = 120
- blastRadius: At high terrain/camera altitude the rain volume rides up with the camera (acdream span camZ120..camZ+695 vs retail fixed 120..+695 world) — subtle density/coverage differences on mountains; nothing user-reported.
- retailEvidence: GameSky::UpdatePosition (0x00506dd0, BN pc:268596-268618): weather objects (props bit 4) snap x,y to the viewer origin; when bit 8 is clear the frame z slot is OVERWRITTEN with the constant 0xc2f00000 = 120.0f (0x00506e96-e98) — an absolute height, not an offset.
- acdreamEvidence: SkyRenderer.RenderPass applies Matrix4x4.CreateTranslation(0,0,120) to the model in a sky view whose translation is zeroed — i.e. 120 relative to the camera (SkyRenderer.cs:175-178, 307-308; the doc comment at 284-306 itself reads the decomp as an offset).
- portShape: In the weather branch, translate by (0,0,120 cameraWorldPos.Z) so the cylinder's base sits at world z 120 while x,y stay camera-snapped — a two-line change in RenderPass.
### [LOW] weather-enabled-toggle-absent (UNVERIFIED (verifier hit token limit)) — No weather_enabled client toggle (weather objects always instantiated)
- blastRadius: None vs retail defaults (retail ships weather on, GameSky::s_weatherEnabled init 0x1 at pc:1098001); only matters for the user-options parity pass.
- retailEvidence: LScape::weather_enabled gates the weather draw (0x0050638b-96, 0x005070d8) and GameSky::CreateDeletePhysicsObjects (0x005073c0, pc:268912-268917) creates/destroys the weather physics objects when the flag flips.
- acdreamEvidence: SkyDescLoader.cs:46-51 documents the missing toggle ('we don't honor a weather_enabled toggle yet'); SkyRenderer partitions purely on the dat property bits.
- portShape: A RuntimeOptions/settings bool consulted at both the object-build site (SkyDescLoader/DayGroup hydration) and the RenderWeather call sites — mirroring retail's create/delete + draw double gate.
## OPEN QUESTIONS
- Is D3DPolyRender::DrawPortalPolyInternal in DrawCells stage (b) (0x005a49b7, second arg 0) strictly a depth-only write? The position in the sequence (after the full z-clear, before shells) and its use with arg 0 vs the ConstructView call site (arg = arg5==1, 0x005a5a7b) strongly imply a z-stamp with color off, but I did not decompile DrawPortalPolyInternal itself to confirm the write mask — confirm before porting the masks stage.
- Default and source of the retail global `cliplandscape` (branch at 0x005a5681 in PView::ClipPortals): when 0 the outside view merges UNCLIPPED (Render::copy_view(this, nullptr, 0) at 0x005a5699). Presumably a registry/debug toggle defaulting to clipped, but I could not find its initializer.
- Does retail's weather pass really composite rain over near buildings outdoors? GameSky::Draw sets DEPTHTEST_ALWAYS around DrawObjCellForDummies(after_sky_cell), but the poly pipeline below DrawObjCell (CShadowPart::draw / D3DPolyRender surface setup) may re-set depth state per surface — not traced to the draw-call level.
- Exact draw-time role of before_sky_cell: GameSky::Draw pass 0 iterates sky_obj directly (skipping bit-0x01 objects) rather than drawing before_sky_cell as a cell, so the cell looks like a positioning/lighting container only — no draw site for it was found, but I did not exhaustively xref it.
- Retail re-centers the landscape block draw list on get_outside_cell_id(viewer) while the viewer is indoors (RenderNormalMode 0x453aa0, gated on viewer_cell->seen_outside); acdream's streamed terrain window is centered on the player landblock. Whether this matters at landblock edges (viewer inside a building near a block boundary, doorway facing the un-streamed direction) is untested.
- #108's final mechanism still needs its own capture: this map identifies the unwired portal z-stamp and the scissored-vs-full depth clear as the faithful-port gaps sitting exactly in that code path, but does not prove which of the two produces the grass sweep during the cellar ascent.
- DrawMesh's per-view loop draws a mesh once per intersecting portal view (0x005a08ae-096f) — when two doorways show the same outdoor object, retail composites it twice under disjoint clips, same as acdream's per-slice redraw; I treated this as a match, but alpha-blended objects at overlapping view edges were not verified pixel-level.