The one-gate rule (feedback_render_one_gate) is now structural:
- The per-frame ACME BFS (CellVisibility.ComputeVisibilityFromRoot) is
GONE from the frame. Its only production consumer was the
cameraInsideCell boolean - which is exactly 'viewerRoot is not null'
(the TryGetCell that produced viewerRoot already proves cells are
loaded; ComputeVisibilityFromRoot returned null iff root was null).
A full second visibility computation ran every frame to derive a
boolean we already had. The method + its tests remain as quarantined
non-production code (dual-live-visibility-computations, confirmed).
- The clipRoot==null mini-pipeline is DELETED (legacy-outdoor-branch-
remnant, adjusted-confirmed): the outdoor partition draw, the
Chebyshev look-in gather, the DrawPortal invocation and the dynamics
fallback. clipRoot is null only when NO viewer cell exists (pre-login,
fly/debug cameras, transient gaps) - those frames draw flat through
the dispatcher; every normal outdoor frame is the outdoor node.
- DELETED with it: InteriorRenderer (class file - its only caller was
the legacy branch), RetailPViewRenderer.DrawPortal +
RetailPViewPortalDrawContext (the look-in product; outdoor-root frames
flood buildings via MergeNearbyBuildingFloods inside DrawInside),
the _exteriorPortal*/_outdoorRootNoCells fields.
Per frame there is now exactly ONE visibility computation
(PortalVisibilityBuilder) and ONE render path (DrawInside).
Suites: build green, App 226 green, Core baseline (1398 + 4 pre-existing
#99-era).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports Render::viewconeCheck (Ghidra 0x0054c250): meshes are sphere-CULLED
per portal view, never hard-clipped. NEW ViewconeCuller lifts each
slice's <=8 clip-space half-planes to world-space eye-edge planes (the
view_vertex.plane analog, acclient.h:32483 - one matrix fold: L = VP
rows . P) and tests bounding spheres from the entity's cached AABB (the
dispatcher's own cull bounds source).
Gating now matches retail's shape end to end:
- Per-cell STATICS: sphere vs THEIR CELL's views - the statics-through-
walls fix (the cottage phantom staircase's actual draw path: a static
outside every view of its cell no longer paints through the wall).
- DYNAMICS (last pass): sphere vs their cell's views; outdoor/unresolved
vs the outside views (pass-all under the outdoor root). A dynamic in a
non-flooded room culls HERE - retail never reaches an object whose cell
is not in the draw list; the partition still routes it so the CULL is
what drops it, retail's shape exactly.
- OutdoorStatic (landscape pass): pre-filtered per outside slice; the
per-slice entity gl_ClipDistance routing is DELETED (entities draw
outside the clip bracket; terrain/sky keep their plane clip).
- PARTICLES: the scissor-AABB gate is DELETED; emitters gate through
their cone-surviving owners (candle-flames-through-walls fix).
- WEATHER: gated on the PLAYER being outside (retail is_player_outside -
an indoor player gets no rain even looking out a doorway). Closes
weather-gate-player-vs-viewer.
- UNATTACHED emitters (campfires) get their missing outdoor-root pass
(closes unattached-particles-dropped-outdoors).
Suites: App 226 green (flood gates included), Core baseline unchanged
(1398 + 4 pre-existing #99-era).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Retail floods a building's interior exactly when its shell DRAWS and an
aperture survives the view: DrawBuilding (Ghidra 0x0059f2a0) -> per-view
viewconeCheck on the shell -> portal-BSP walk -> ConstructView(CBldPortal)
side test + GetClip-vs-view + GetVisible. There is NO distance constant
anywhere on that chain (verifier-confirmed, flood-gate-shape adjusted).
Port:
- GameWindow's outdoor-node gather: per-BUILDING frustum pre-gate on the
aperture bounds (Building.PortalBounds - the tight flood-purposes
equivalent of the shell viewconeCheck), iterating the per-landblock
BuildingRegistries. Replaces the Chebyshev<=1 landblock cell-sweep.
Also the proper fix for the 2026-06-07 'FPS drops when I look out'
problem the Chebyshev hack approximated: dozens of AABB tests instead
of an O(all loaded cells) portal sweep.
- OutdoorBuildingSeedDistance 48f -> infinity (the binary visibility pop
at ~48 m - the confirmed #109 mechanism candidate - is gone; admission
is now the screen clip per portal, retail's GetClip gate).
- The legacy clipRoot==null look-in path keeps its 48 m: it is T4
deletion scope; improving doomed code wastes effort.
Closes the building-flood-seeding-48m-cutoff divergence (culling area,
adjusted-confirmed). Suites: App 226 green (flood gates included).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
IN (retail-faithful):
- Growth propagation is IN PLACE, never by re-enqueue: retail
AddViewToPortals (Ghidra 0x005a52d0) enqueues only on first discovery
(InsCellTodoList); growth into a popped cell runs AdjustCellView -
re-clip ONLY the new views through that cell's portals immediately.
ProcessCellPortals + the processedViewCounts watermark port exactly
that; the MaxReprocessPerCell=16 drift cap is DELETED (the 1-px merge
is the physical fixpoint floor). Depth-128 tripwire logs loudly if the
convergence invariant ever breaks (failsafe, not control flow). Same
restructure in BuildFromExterior.
- Reciprocal-empty culls strictly (retail OtherPortalClip returning
nothing = the opening is invisible from the neighbour's side); the
eye-in-opening pre-clip restore is gone.
- Look-in seeds: retail ConstructView(CBldPortal) IN_PLANE reject at the
TRUE F_EPSILON (SeedInPlaneEpsilon=0.0002, const @0x007c8c70) + the
full-screen substitute rescue DELETED (the verifier-flagged non-retail
bypass that admitted floods retail strictly rejects).
REFUTED BY THE CONFORMANCE GATE (attempted, reverted, documented inline):
- PortalSideEpsilon 0.0002: retail's tight epsilon assumes the viewer
cell transits the instant the eye crosses a plane; our root can lag
~1 cm at pressed corners (CornerFloodReplay failed at every step -
0x0171/0x0173 chain dead). 0.01 KEPT as the documented root-lag
tolerance; tighten only with eye-exact viewer tracking + cdstW.
- Deleting the clip-empty eye-in-opening rescue: same gate, same total
failure - our ProjectToClip near-eye behavior (EyePlaneW=1e-4) diverges
from retail polyClipFinish's UNPINNED cdstW constant. Rescue KEPT as
the documented cdstW-gap compensation; re-attempt only after pinning
cdstW from the binary.
Gates: App 226/226 green (CornerFloodReplay + MeetingHallFlood + the
collapsed-portal pin all pass); Core baseline unchanged (1398 + 4
pre-existing #99-era). CloneViewPolygons orphan removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(a) MergeBuildingFrame now UNIONS a building flood's views into cells
already present in the frame (retail Render::copy_view APPENDS every
clipped portal polygon as a new view_poly, Ghidra 0x0054dfc0 - a cell
visible through two apertures holds two views). The old first-wins
'ContainsKey -> continue' dropped the second aperture's views: the
multiview-loss-first-wins divergence, a named #109 suspect.
(b) ClipToRegion output now runs retail's post-divide vertex merge:
consecutive vertices closer than ~1 pixel collapse (copy_view's
|dx|<=1 && |dy|<=1 screen-unit merge), polygons that collapse below 3
distinct verts return empty (retail's '<3 survivors -> count 0'). This
is the flood's PHYSICAL fixpoint floor - re-clipping can only insert
sub-pixel slivers, which the merge removes, so accumulated views
converge instead of drifting. Unit note: builder has no viewport, so
1 px is expressed as NDC at reference 1080p (0.00185); coarser at higher
res, which only strengthens convergence. This is the prerequisite for
removing the MaxReprocessPerCell=16 drift cap (T2 slice 2) and the
EyeInsidePortalOpening rescue.
Gates: all 10 flood conformance tests green (CornerSweep monotone pin
included); App 226 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The complete retail drawing order in one installment (per the amended plan:
every installment is a COMPLETE retail behavior - the half-ported punch of
88be519 is re-landed here WITH the ordering that makes it correct):
static world (sky/terrain/weather/shells/scenery)
-> aperture depth writes (interior SEAL at true depth / outdoor+look-in
PUNCH to far-Z; PortalDepthMaskRenderer, DrawPortalPolyInternal
Ghidra 0x0059bc90)
-> interior cells WHOLE, far-to-near, drawn once (DrawCells Loop 2,
Ghidra 0x005a4840; use_built_mesh pc:427905)
-> per-cell STATIC object lists
-> ALL dynamics LAST (DrawDynamicsLast), depth-tested, never hard-clipped
InteriorEntityPartition: new contract - every ServerGuid != 0 entity goes
to Dynamics regardless of cell (indoor/outdoor/unresolved/hidden); ByCell
carries only dat-baked indoor statics of visible cells; Outdoor renamed
OutdoorStatic. Fixes the audit's livedynamic-invisible-under-interior-roots
divergence as a side effect (live entities are never dropped by the
visibility set; culling is T3's viewcone).
DELETED (retail has no counterpart): the gl_ClipDistance shell chop
(927fd8f enable + 9ce335e outdoor scoping + UseShellClipRouting + the
per-slice shell loop + clipShells param) - retail never clips cell
geometry; aperture exactness = punch/seal + z-buffer + this order. The
old per-slice scissored AABB depth clear is replaced by retail's single
gated full clear (ClearDepthForInterior). The interior-root LiveDynamic
top-up draw and the look-in's dynamics involvement are gone (one last
pass, no double-draws).
Closes at the T5 gate (expected): #114 (chop deleted), the char-eaten-by-
doorway regression (ordering), outdoor interiors-through-doorways (punch);
#108's render half (seal) - its membership half stays re-attributed.
Suites: build green, App 226 green (partition tests rewritten to the T1
contract), Core 1398 + 4 pre-existing #99-era + 1 skip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'I don't care if it is non-playable... I want everything ported, then we
test.' Per-phase playability + per-phase visual gates DROPPED. BR-2..BR-6
execute as ONE continuous port with build+tests green per commit and a
single comprehensive visual pass at the end (T5). Replaces the
playability rule with: every installment must be a COMPLETE retail
behavior, never half of one (the BR-2 punch-without-ordering lesson,
88be519).
The BR-2 punch/seal gate proved #108 (cellar grass-sweep) is a membership
flip (player classified outdoor mid-cellar), not a render depth bug. The
punch only masked it on outdoor-root frames. Move #108 to the membership
track; the interior depth seal is a separate mechanism that does not fix it.
Visual gate (2026-06-11) on the seal+punch build, then on the punch-reverted
build, isolated the truth:
- With the punch wired: #108 (cellar grass-sweep) gone BUT the player/NPCs
go transparent by exactly their overlap with any doorway viewed from
outside (the far-Z punch erases the depth of dynamic objects standing in
the aperture, so the interior paints over them).
- With ONLY the punch reverted (seal+full-clear kept): characters render
correctly AND #108 is BACK.
The punch is wired for OUTDOOR roots + the look-in path ONLY; it never runs
on a clean interior (cellar) frame. For it to have suppressed #108, the
cellar-transition frames must render through the OUTDOOR root -> the player
is being classified OUTDOOR mid-cellar (the known #112/#106 cellar
membership ping-pong). So:
- #108 is a MEMBERSHIP bug (render is downstream of membership); the punch
was MASKING it, harmfully. Re-attributed to the membership track.
- The interior-root SEAL addresses a case that is NOT #108 (confirmed: #108
isn't an interior-root frame), so it has no verified visible effect yet.
Per no-workarounds + verify-before-layering: reverted ALL of BR-2's depth
machinery (seal, punch, the per-slice->full-clear swap) to the pre-BR-2
baseline (restored from 6cba950). The phantom-site probe (6cba950) is kept.
PortalDepthMaskRenderer.cs is KEPT as a RESERVED, unwired primitive (it is
verified-correct; the depth discipline will be rebuilt during BR-3 with
dynamics-after-interior ordering, where it can be verified against the
shell-chop deletion).
What survives from this session's execution: BR-1 (already-equivalent,
695eca2) stands. #108 moves to membership. BR-2 to be re-approached under
BR-3 with correct ordering. No net production behavior change vs 6cba950.
Suites: build green, App 226 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirror of commit 1's seal: the punch half of retail's invisible portal
depth writes (DrawPortalPolyInternal maxZ1, Ghidra 0x0059bc90;
ConstructView(CBldPortal) mode-1, pc:433827).
Generalized DrawRetailPViewExitPortalSeal -> DrawRetailPViewPortalDepthWrite
with retail's maxZ1/maxZ2 selector (forceFarZ):
- INTERIOR root: forceFarZ=false (SEAL, true depth) - unchanged from c1.
- OUTDOOR root + DrawPortal look-in: forceFarZ=true (PUNCH, far depth) -
erase the terrain depth inside a flooded building's entry aperture so
the interior shows THROUGH the doorway against the nearer front-ground.
Architectural note (divergence from retail ORDER, same RESULT): retail
draws the shell LAST (DrawBuilding Draw(part,1) punch -> draw interior ->
Draw(part,0) shell) so the shell closes everything outside the punch. Our
pipeline draws the building shell FIRST (it is an outdoor WorldEntity in
the landscape pass), so the outside-the-aperture wall occlusion is already
in the depth buffer when interiors draw - we need ONLY the punch for
in-aperture visibility, no shell reorder. The punch is confined to each
door polygon clipped to its slice (NOT a full clear), so it does not
reintroduce the 'cellar paints over everything' hazard that gated the old
outdoor ClearDepthSlice to null.
DrawExitPortalMasks is now wired on the outdoor-root DrawInside context and
the DrawPortal look-in context (both previously null -> no-op).
Suites: build green, App 226 green, Core 1398 + 4 pre-existing #99-era.
NEEDS VISUAL GATE (batched with BR-3): outdoor interiors-through-doorways
must not bleed; cellar grass-sweep (#108) gone; tower stairs near+far.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the seal half of retail's invisible portal depth writes
(D3DPolyRender::DrawPortalPolyInternal, Ghidra 0x0059bc90; dispatched by
PView::DrawCells loop 1, Ghidra 0x005a4840 pc:432783-432786):
- NEW PortalDepthMaskRenderer: draws a portal polygon as a color-masked
triangle fan, depth-test ALWAYS + depth-write ON, at the polygon's TRUE
projected depth (retail maxZ2 seal) or forced to far-z 0.99999988
(retail maxZ1 punch - the constant from 0x0059bc90's tail; punch wiring
lands in BR-2 commit 2). Where retail software-clips the fan against
the installed view (polyClipFinish), we apply the SAME slice region via
gl_ClipDistance from the slice's <=8 clip-space half-planes. GL state
fully self-contained (set -> draw -> restore, no early-outs).
- DrawExitPortalMasks is now WIRED in production (was a null-callback
no-op since birth): for interior roots, every visible cell's portals
with OtherCellId==0xFFFF get their world-space polygon sealed per view
slice, far-to-near, after the landscape slices.
- ClearDepthSlice (per-slice scissored AABB clear - wrong shape, wrong
scope, no seal after it) is REPLACED by ClearDepthForInterior: ONE
full-buffer depth clear between the outside stage and the interior
stage, gated on any outside slice having drawn (retail's
portalsDrawnCount gate semantics staged as an open question, marked
inline). DepthMask(true) asserted at the clear site (c4df241 lesson).
Outdoor roots: no clear, no seals (interiors must depth-test against
terrain until the commit-2 punch).
Closes the mechanism behind #108 (outdoor grass sweeping across the
upstairs door opening - terrain depth seen through the doorway is now
re-stamped at the door plane so farther interior geometry z-fails inside
the aperture). Visual gate: BR-2/BR-3 batched checklist (cellar doorway
+ cottage wall + tower stairs near/far).
Suites: build green, App 226 green, Core 1398 + 4 pre-existing #99-era
failures + 1 skip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The BR-1 pre-check left the #113 phantom residual with two surviving
suspects, both cell-side: (a) flood-admitted cells whose shell draws with
a pass-all slice (NoClipSlice fallback when the assembler handed no slot,
or an assembler slot-0 scissor-fallback slice), and (b) cell entity
buckets drawn unclipped + un-viewcone'd by design.
[phantom-shell]: per shell-pass cell, print-on-change - clip-enable
state, slot presence, every drawn slice's slot + plane count with
PASS-ALL flagged. [phantom-objs]: per object-list cell, print-on-change
- entity bucket size. Env-gated ACDREAM_PROBE_PHANTOM=1, zero cost off,
throwaway (strip when the phantom closes).
Repro protocol: launch with the probe on, stand at the hall bisect spot
(world ~216,-108 looking at the AAB3 meeting hall west face) where the
phantom is visible, read which mechanism fires for stair cells
0xAAB30100..0x106. Shells pass-all -> BR-2/BR-3 close it; statics ->
BR-5 closes it.
Build green; App suite green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The plan's BR-1 ('implement the skipNoTexture draw-time surface gate')
died on its pre-check: acdream ALREADY suppresses every portal fill.
ReplicateProductionEmission_OnPortalFills replicates the exact emission
conditions of the production extractors on the hall/cottage fills:
pos=False neg=False for every one (Stippling.NoPos skips the positive
side at ObjectMeshManager.PrepareGfxObjMeshData:1046,
PrepareCellStructMeshData:1394, CellMesh.Build:44, GfxObjMesh.Build:71;
the fills have no negative surface). There is nothing to gate.
What ships instead: StipplingSurfaceEquivalenceTests - 2,607 polys across
13 building models + 13 environments, ZERO violations both directions:
NoPos <=> untextured-surface. Our build-time skip is proven equivalent to
retail's draw-time skipNoTexture rule (Ghidra 0x0059d4a4, default on
@0x00820e30) on this content. The pin fails loudly if future content
breaks the invariant - the cue to implement the draw-time gate then.
Corrections folded into the plan + comparison docs:
- The #113 phantom residual CANNOT be GfxObj fills (they never reach a
vertex buffer). Plausible true sites are cell-side: flood-admitted
cells drawn with the pass-all NoClipSlice when slot-less
(RetailPViewRenderer.cs:71), and/or cell statics drawn unclipped +
un-viewcone'd (object-lists-skip-portal-view-gate, confirmed).
BR-2 opens with the probe that pins which.
- The e46d3d9 user-gate observations (filter removed phantom/doors) were
confounded - the filter was a provable mesh no-op on shells AND doors.
- Ledger rows solid-surface-skip-missing + the acdream half of
portal-polys-baked-unconditional re-marked REFUTED-for-fills; the
retail mechanism descriptions and the un-consumed PortalIndex->
CBldPortal pairing (BR-4) stand.
Suites: Core 1398 green (1392 baseline + 6 new facts) + the 4 pre-existing
#99-era failures + 1 skip. No production code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Names the boundary of what BR-1..BR-8 delivers, so the gaps are written
down rather than silently assumed (the very thing that breeds whack-a-mole):
FU-1 transparency/sorting (BR-9 candidate, area unmapped), FU-2 dungeon
visibility scaling #95 (plausibly helped by BR-4/BR-6 but NOT guaranteed -
re-measure after), FU-3 LOD/degrades, FU-4 picking, FU-5 the ~30 open
questions (in the comparison doc $6), FU-6 verification top-up before
BR-8b lighting. None blocks BR-1..BR-8; each becomes its own item.
The #95 dungeon-scaling follow-up was previously raised only verbally -
now tracked in the plan. Sections 4/5 renumbered to 5/6.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Phase A confirmation fact (DumpPortalFillSurfaceTypes): every portal-fill
polygon on the audited building models (hall 0x010014C3, cottages
0x01000827/0x0100082E/0x01000C17) carries an untextured surface
(Base1Solid, mostly +Translucent) with Stippling=NoPos and no negative
surface. Retail's skipNoTexture rule (D3DPolyRender inner draw 0x0059d4a0,
default on @0x00820e30) therefore skips ALL of them on the building/cell
pass - door fills, window fills, AND the phantom stair-ramp. Retail never
draws any baked fill; visible doors are door ENTITIES. acdream draws the
solid batches as colored geometry, which is both the phantom staircase AND
why dropping them read as 'doors disappeared'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Charter open mystery #1 (docs/research/2026-06-11-building-render-holistic-
port-handoff.md "4.1): the e46d3d9 DrawingBSP poly filter made doors vanish
because the PosNode/NegNode walk only collected node.Polygons and never
node.Portals (List<PortalRef> {PolyId, PortalIndex}).
Dat-proven across all 13 Holtburg-area building models (A9B4/A9B3/AAB3/
A9B5/AAB4): TRUE-orphans = ZERO everywhere. Every dictionary poly the
filter dropped is a PORTAL POLYGON - the baked door-filling (1.9x2.5 m)
and window-filling quads at doorway/window apertures, AND the meeting
hall's phantom stair polys {0,1} (ramp-shaped portal apertures into the
interior stair cells).
Consequences for the holistic port:
- The door entities (setup 0x020019FF) were never affected: base parts +
every degrade variant have full BSP coverage, and doors don't take the
IsIssue47HumanoidSetup degrade swap. The vanished 'doors' were shell
portal polys.
- Retail draws portal polys CONDITIONALLY during portal-view traversal
(closed doors/windows draw a surface; open apertures and the hall's
stair apertures don't). The phantom staircase and the door rendering
are the SAME mechanism with opposite signs - there is NO correct
static filter; this is the dat-side proof the one-drawing-discipline
port is required.
- The exact retail conditional (BSP portal-node draw gate in
CPhysicsPart::Draw / BSPPORTAL) is a named Phase A question.
Diagnostic-only commit: new dump facts in
Issue113DoorVanishDiagnosticTests (door setup + degrade chains, control
models, Holtburg orphan sweep with portal discrimination). No production
code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
User mandate: stop bug-by-bug; map acdream-vs-retail for building draw,
interiors, interior collision, dynamics, clipping, culling; plan the port
of retails drawing discipline once and for all. The handoff carries the
branch state (124c6cb, nothing on main), the full evidence inventory from
this session (orphan no-draw polys, door-vanish mystery, draw-side clip
status, straddle gate), the gap map, tooling (Ghidra MCP 8081 correct
PDB, live cdb protocol, dat dump + flood harnesses), the investigation
charter (workflow fan-out per subsystem, adversarial verification), and
the paste-ready new-session prompt. #113 marked REOPENED and folded in.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
User gate 2026-06-11: the filter (e46d3d9) removed the phantom staircase
everywhere (verified) but DOORS disappeared across Holtburg - the naive
PosNode/NegNode walk evidently misses polygons some models reference
another way. Doors > phantom stairs: filter application removed; the
CollectDrawingBspPolygonIds helper and the dat-fact tests (hall orphans
0+1, cottage 0..7) stay as apparatus for the holistic building-render
port. First diagnostic for re-landing: run the DrawingBSP histogram on a
door GfxObj. See
docs/research/2026-06-11-building-render-holistic-port-handoff.md.
Branch state after this commit: outdoor-scoped shell clip (927fd8f +
9ce335e) + retail straddle gate (414c3de) + all diagnostics; phantom
staircase VISIBLE again (known, documented); doors functional.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The user gate + bisect overturned the coincident-cell attribution: the
phantom staircase persists in the PRE-session build (bisect screenshot at
the hall wall) and is drawn by the ENTITY pipeline, untouched by any clip.
Root cause (dat-proven, DumpHallModel_PolyFlagHistogram): retail renders a
GfxObj by TRAVERSING its drawing BSP (D3DPolyRender); polygons present in
the Polygons dictionary but referenced by NO DrawingBSP node are never
drawn - they are physics/no-draw geometry. The Holtburg meeting hall
(0x010014C3) keeps its exterior stair-ramp as dictionary polys 0+1: in
the PhysicsBSP (ACE walks The Sentry on it at z 117-118; invisible-but-
walkable in retail) but orphaned from the draw tree (true at ALL degrade
levels - the LOD theory is dead, Degrades[0] IS the base model). The hill
cottage (0x01000827) carries 8 such orphans. Our extraction iterated the
dictionary -> drew the collision skeleton: the wall staircase up close,
the flying stairs over the cottage roofline from afar (orphan ramp spans
world 221-232 at z 116-124.5; visible over the cottage roof from the west).
Fix: PrepareGfxObjMeshData filters to CollectDrawingBspPolygonIds(gfxObj)
when a drawing BSP exists; models without one draw everything (unchanged).
Physics untouched (collision keeps the full physics set - retail parity).
CellStruct extraction not touched (different conventions; no orphan
evidence there yet).
Dat-backed pins: Issue113DrawingBspFilterTests (hall orphans == 0+1,
cottage orphans == 0..7). Suites: App 226 / Core 1392 + the 4
pre-existing #99-era failures / UI 420 / Net 294.
Note: the earlier shell-clip enable (927fd8f, scoped 9ce335e) remains
correct and orthogonal - it crops interior CELL geometry to apertures
outdoors; this commit removes the phantom SHELL geometry at its source.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 2026-06-11 user gate on 927fd8f: the OUTDOOR half works (phantom
meeting-hall staircase GONE at the original spot) but enabling the clip
for INDOOR roots exposed that our indoor clip regions are admission-
quality, not draw-quality - chopped interior stairs, a neighbour rooms
barrel visible through a clipped-away wall, missing candle-holder
geometry, inner walls vanishing while passing building exits. Retail
crops indoors too, but with pixel-exact recursively-clipped regions;
ours have knife-edge cases indoors that were invisible until the GL
enable made them cut real geometry.
Scope: DrawInside enables the shell clip only when RootCell.IsOutdoorNode
(the regime Issue113MeetingHallFloodTests validates); DrawPortal (from-
outside look-in) keeps it on; indoor roots draw unclipped - yesterdays
user-accepted state. Filed #114 for bringing indoor regions to retail
crop quality (also the home of the remaining gate findings to re-test:
entry-transparency at the hilltop cottage, particles visible through
buildings, camera feel in cramped interiors).
App 224 green; no Core/UI/Net surface touched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#113 moved to Recently closed: the phantom staircase was the Holtburg
meeting hall (AAB3, not A9B3) interior stair cells drawn unclipped from
outside - the PView shell clip was routed but never GL-enabled (927fd8f).
Misplaced-cell hypothesis refuted with dat evidence. #112 residual
paragraph updated: retail straddle gate live-binary verified + ported
(414c3de); at-doorway demote is retail-faithful, deep gaps now keep-curr.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The oracle read the #112 residual was waiting on, settled against the
LIVE 2013 client (cdb attach, CEnvCell::find_transit_cells @ 0052c820;
BN pseudo-C was ambiguous and partly wrong per
feedback_bn_decomp_field_names - it invented portal_side tests in this
branch): retail admits outdoor transit cells from an indoor cell IFF a
path sphere STRADDLES an exterior portal polygon plane,
|dist| < radius + F_EPSILON(0.000199999995, @ 007c8c70). The flag at
[esp+18h] (set 0052c925, x87 decode fcompp/test ah,41h +
fcomp/test ah,5/jp) gates the add_all_outside_cells call (0052c9d6 je).
Graph reachability alone NEVER admits outdoor cells in retail.
Port (CellTransit):
- FindTransitCellsSphere: exitOutside now carries the retail straddle
semantics; new hasExitPortal out carries the old topology-only flag.
- BuildCellSetAndPickContaining: the collision cell SET keeps the A6.P5
topology widening on hasExitPortal (outdoor-registered doors must stay
findable from indoor cells until #99/A6.P4 ships per-cell shadow
lists - the 2026-05-25 door capture scenario), but the membership
PICK's outdoor branch is gated on the retail flag. Membership is now
retail-identical in both regimes: straddle -> outdoor candidates valid;
no straddle -> outdoor ignored -> retail keep-curr. This is what stops
deep-interior containment gaps in ANY house from demoting to outdoor
(the #112 transparent-interior shape) - the systemic protection the
user asked for, without house-by-house verification.
The at-doorway A9B3 gap demote is RETAIL-FAITHFUL (gap point is 0.23m
from 0x104s door plane < 0.48 foot radius -> retail straddles + demotes
+ self-heals inward): DocumentsResidual renamed to
...DemotesRetailFaithfully, expectation unchanged. New conformance pins:
deep-gap keep-curr (A9B3Cottage_GapBeyondStraddleDistance_KeepsCurrCell)
+ function-level gate semantics on real dat geometry
(FindTransitCellsSphere_ExitPortalStraddleGate_MatchesRetail).
Tests: Core 1391 green (+2) / App 224 / UI 420 / Net 294; pre-existing
4 #99-era failures unchanged; P1 membership goldens + A6.P5 door-set
tests explicitly green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Attribution (dat-evidenced, supersedes the misplaced-cell hypothesis):
the phantom staircase is the Holtburg MEETING HALL (AAB3 building[0],
model 0x010014C3 at AAB3-local (36,84,116)), NOT an A9B3 building - the
user stood at the A9B3/AAB3 boundary (cell-transit trail in
issue112-gate1.log) and clicked through the hall to the NPC behind it.
The hall's interior stair cells (0x100..0x106, ring climbing z 116->124.5
to the deck hatch) have geometry coincident with the shell's west wall
(both at local x=29.0). Our outdoor per-building flood admits them with
CORRECT tight clip regions (4-6 planes, door-aperture NDC boxes -
Issue113MeetingHallFloodTests proves it), but DrawEnvCellShells drew them
WHOLE: mesh_modern.vert writes gl_ClipDistance from the routed CellClip
slot, and gl_ClipDistance is ignored unless GL_CLIP_DISTANCEi is enabled -
which no caller ever did for the shell pass (born inert in 1405dd8).
Interior staircase painted across the exterior wall; unpickable because
it is cell geometry, not an entity.
Retail oracle: cell geometry IS clipped to the accumulated portal view -
Render::set_view (:343750) installs the view polygon edge planes,
DrawEnvCell submits every cell polygon with planeMask=0xffffffff (:427922)
through ACRender::polyClipFinish. Characters/meshes are NOT poly-clipped
(viewconeCheck path) - entity routing stays cleared, comment scoped.
Fix: enable GL_CLIP_DISTANCE0..7 around exactly the shell pass
(self-contained per feedback_render_self_contained_gl_state; no early-outs
between set and restore). Slot-0 fallback slices (>8-plane regions) still
draw pass-all - the assembler's scissor fallback remains unimplemented and
documented; the new flood test pins 0 such slices at the hall.
Refuted along the way (full evidence in Issue113PhantomStairsDumpTests):
- ONE misplaced interior EnvCell unifying #113+#112+collision gaps: all 17
A9B3 cottage cells share an identical dat Position (nothing to misplace);
the #112 gap is a real 20cm doorway micro-gap 0.23m outside threshold
cell 0x104 (straddles its exterior portal plane at foot radius 0.48);
missing object collision remains #99/A6.P4.
- A9B3 dat content near the spot: no stair geometry in shell (balcony at
z119 + turret roof only), cells (flat 116/118.8), statics, or stabs.
Tests: Core 1389 green (+6 dump facts) / App 224 (+1 flood replay) /
UI 420 / Net 294; pre-existing 4 #99-era failures unchanged.
Visual gate pending: user re-check of the hall west face vs retail.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause (oracle: CLandCell::point_in_cell :316941 = terrain-poly only;
find_cell_list null-result keep-curr pc:308788-308825; CEnvCell::
check_building_transit :309827 = sphere_intersects_cell per portal-adjacent
cell): retail KEEPS curr_cell when nothing contains the centre — including
inside a house's containment gaps. Our 6dbbf95 escape hatch instead demoted
any hydrated indoor claim the sphere no longer overlaps to the outdoor
column; at the A9B3 hill cottage's real interior gap this stranded the
player outdoor-classified deep indoors, where re-promotion is portal-
adjacent-only (retail-identical) -> the outdoor flood rendered the interior
transparent (the user's "sometimes transparent" walk).
The hatch's actual target - poisoned (cell, position) SAVES - has been
handled at the SNAP by PhysicsEngine.Resolve's AdjustPosition validation
since #107/#111, so the per-tick pick reverts to retail semantics:
1. lateral recovery first - when the sphere no longer overlaps the claim,
search the claim's stab list for a containing cell (retail
find_visible_child_cell :311444, the same recovery AdjustPosition uses);
the #111 adjacent-claim shape now self-heals laterally (dat-backed test:
pick(seed 0x172) at a 0x171-interior point -> 0x171);
2. else KEEP curr_cell (retail null-result).
Two old tests asserting the hatch demote rewritten to the retail semantics
(tests-can-codify-bugs); P1 retail-golden conformance gates explicitly green
(FindCellListConformance + ThresholdPortalCrossing + CottageDoorway +
CameraCornerSeal = 11/11). New Issue112MembershipTests: the lateral-recovery
fact + a DocumentsResidual fact pinning the remaining at-doorway gap demote
(via the NORMAL outdoor-candidate path; open oracle read = retail's
add_all_outside_cells gate in CEnvCell::find_transit_cells pc:317499 -
sphere-proximity vs graph-reachability). Core 1383 + 4 pre-existing #99
failures + 1 skip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The entry snap set only the controller (physics stood on the grounded floor)
while the renderer kept drawing the entity at the server-restored height -
the user's "spawned 2 meters in the air" screenshot over a fully-correct
interior. The teleport-arrival path already does entity.SetPosition +
ParentCellId (GameWindow.cs:4914); the login path now matches.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two grounding selection rules failed against live ACE restores before the
right one: (1) first-hit SampleFloorZ returned 0x171's 99.475 ceiling TOP face
over its 94.0 floor (issue111-verify2.log) - the player committed onto the
roof level, and the session's heartbeats poisoned ACE's save with z=99.475;
(2) nearest-to-reference self-confirmed that poison (the reference SAT on the
ceiling face, issue111-verify3.log). Root insight: ceiling/roof top faces are
upward-facing and XY-projectable - geometrically indistinguishable from
floors in the render-ish CellSurface soup. The PHYSICS walkable set (plane
normal.Z >= PhysicsGlobals.FloorZ over the claim's Resolved cell-local
polygons - retail BSPTREE::find_walkable's filter) contains only real floors:
PhysicsEngine.WalkableFloorZNearest transforms into the cell frame, drops on
each walkable plane under the XY, picks nearest the reference.
Verified live (issue111-verify4.log): ACE restored the roof-poisoned
(0xA9B40171, z=99.475); the snap validated the claim and grounded to
z=94.000 - the first fully clean indoor login of the arc:
[snap] claim=0xA9B40171 VALIDATED -> grounded to its walkable floor z=94.000
[cell-transit] 0x00000000 -> 0xA9B40171 pos=(155.525,12.416,94.000)
Baseline: Core 1381 + 4 pre-existing #99 failures + 1 skip; App/UI/Net green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The [snap] apparatus (issue111-snap1.log) caught the mechanism live: ACE
restored a CLEAN pair (0xA9B40171, on-floor) which AdjustPosition validated -
and the legacy Resolve then committed 0xA9B4013F instead: its bestCell floor-
pick scans EVERY CellSurface in the landblock (123 at Holtburg) for "any floor
under this XY nearest currentZ" and breaks same-height ties by iteration
order. The wrong cell then fails containment on the first movement -> outdoor
demote inside the building -> the #111 transparent interior. This free-pick
also explains the earlier "committed verbatim" mystery (the winning tie
happened to echo the input pair) AND seeded the ACE poison loop: our outbound
heartbeats reported the clobbered cell, ACE persisted it, and the NEXT login
inherited it (this run's [spawn-adjust] rejecting 0xA9B4013F is exactly that
echo coming back).
Fix (retail SetPositionInternal shape): when AdjustPosition VALIDATED an
indoor claim, the cell choice is settled - the snap grounds Z onto the
validated cell's own floor (find_valid_position's settle role, :283426) and
returns; it never re-picks the cell from floor geometry. Claims whose cell has
no own floor surface under the XY (thresholds, stair lips) fall through to
the legacy path unchanged; mover-shaped calls (delta != 0, tests) untouched.
[snap] diagnostic kept (snaps only - one line per login/teleport).
Baseline: Core 1381+4 pre-existing #99 failures+1 skip; App/UI/Net green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gate-run finding (wedge-107-gate-indoor-login.log + dat scan): ACE saved the
cell one room off (claim 0xA9B40172, position inside 0xA9B40171 — adjacent
rooms). FindVisibleChildCell corrects this fine when hydrated (proven by the
new AdjacentRoomClaim regression test), but the live entry committed the raw
claim: IsSpawnCellReady's "any cell struct in the landblock => claim is bogus,
proceed" disambiguator observed the MID-POPULATION state (interiors hydrate in
id order on the background worker; the render-thread predicate read the cache
mid-loop) and opened the gate before the claim and its stab neighbors were
cached. AdjustPosition then saw a null cell struct and silently passed the
claim through; the first movement demoted the player to outdoor inside the
house — the user-visible "transparent interior, see straight through walls"
(render is downstream of membership: an outdoor-classified viewer only sees
the interior through the doorway flood).
Fix: the hold now waits for THE CLAIMED CELL's struct, full stop
(IsSpawnCellReady simplification; HasAnyCellStructInLandblock removed).
Claims that can never hydrate are filtered by GameWindow against the dat's
LandBlockInfo.NumCells range (memoized IsSpawnClaimUnhydratable), and
PhysicsEngine.Resolve carries a loud lost-cell-equivalent safety net: an
indoor claim with NO cell struct AND NO CellSurface floor data demotes to the
outdoor landcell with a [spawn-adjust] line instead of committing raw
(retail GotoLostCell :283418; documented divergence). Partial hydration
(CellSurface present, struct pending) keeps the legacy floor-snap behavior —
HasCellSurface uses the file's masked-low-word norm so bare-id fixtures and
full-id production both resolve.
Baseline restored: Core 1381 (+4 new #107 conformance tests) + 4 pre-existing
#99-era failures + 1 skip; App 223 / UI 420 / Net 294.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause (capture resolve-107-login1.jsonl + dat conformance scan): ACE
restored the player with a POISONED (cell, position) pair — cell 0xA9B40162
(one building) with a position inside 0xA9B40171 (a different building 55 m
away). Our entry snap trusted the claim verbatim: the player stood
fake-grounded for minutes (isOnGround passthrough, no contact plane, no
walkable polygon — zero-move resolves short-circuit), the FIRST movement input
ran a real transition, the pick demoted the indoor claim to outdoor
mid-building, and the player fell 2.4 m through the cottage floor onto the
terrain underneath — wedged inside the building shell. The second wedge shape
(flood-fix-gate2.log) was the PortalSpace freeze: the teleport-arrival
detection gated on `differentLandblock || farAway>100m`, an invented
heuristic — ACE's same-landblock short-hop corrections matched neither, so
PortalSpace never exited and movement input stayed frozen all session.
Four legs, all retail-anchored:
1. PhysicsEngine.Resolve (the player snap path: login entry + teleport
arrival) now runs AdjustPosition first — retail SetPositionInternal step 1
(acclient :283892, AdjustPosition :280009): validate/correct the claimed
cell from the foot-sphere center BEFORE any physics. Corrections log one
[spawn-adjust] line.
2. AdjustPosition's previously-deferred indoor seen_outside →
adjust_to_outside sub-fallback (:280037-280046) is completed; CellPhysics
gains the SeenOutside flag (dat EnvCellFlags.SeenOutside) cached in
CacheCellStruct. The camera path does not reach this sub-branch in the
gated scenarios (CameraCornerSealReplayTests green).
3. PortalSpace arrival = ANY player position update (holtburger PlayerTeleport
handler conformant; recenter still only on landblock change). Verified
live: ACE sent a same-lb dist=69.8 correction that the old gate would have
frozen on — it now completes.
4. Outbound wire (cell, position) pairs are now SELF-CONSISTENT: derive the
landblock frame from the resolver's full cell id instead of welding a
position-derived landblock onto its low word — the old composition could
write exactly the poisoned pair shape into ACE's character save. Plus the
#106-gate-2 hold extension: an indoor spawn claim waits for the claimed
cell's hydration (IsSpawnCellReady) so the validation can act — the async
equivalent of retail's synchronous cell load.
Live verification (wedge-107-verify1.log): entry clean; ACE's same-lb teleport
correction completed (old code: permanent freeze); the teleport destination
itself carried ANOTHER poisoned claim (0xA9B40150) which [spawn-adjust]
corrected to 0xA9B40019; player fully controllable, walking across landcells.
3 new dat-backed conformance tests pin the poisoned-pair facts
(Issue107SpawnDiagnosticTests). Baseline: 1380+4 pre-existing #99-era
failures+1 skip / 223 / 420 / 294.
Pending user gate: park indoors, log out gracefully, relaunch — expect a clean
indoor spawn standing on the interior floor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 137b4f2 payload, re-landed now that #110 is resolved: the missing-indoor-
textures correlation was the pre-existing #105 staged-texture-flush drop
(fixed in c787201), not a near-plane mechanism. znear=0.1 merely raised #105's
trigger probability — a closer near plane makes close-up geometry newly
visible, inflating per-frame prepare/upload pressure indoors and growing the
never-flushed tail. Exactly the handoff's only-credible-link hypothesis,
verified instead of assumed.
Retail: Render::SetFOVRad sets znear=0.1 flat (decomp :342173, initializer
:1101867). 0.1 < the 0.3m camera-collision sphere, so a wall the collided eye
presses against no longer falls inside the near plane — the §4 corner
see-through-wall closes.
Verification on the 0.1 arm (the arm that struck 2-of-3 on 2026-06-10):
nearplane-reland-1.log — [tex-flush] after=0 on all 45 lines, 68,291 [shell]
lines with zero zh>0 batches, all four dat tripwires silent, no [wb-error].
ISSUES.md: #105 + #110 moved to Recently closed with root cause + evidence.
Pending user re-gate: corner press (wall stays solid) + distance scan for
z-shimmer (none expected; retail ships 0.1 with D24).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause: TextureAtlasManager.AddTexture only STAGES texture content (PBO
write + ManagedGLTextureArray._pendingUpdates); the actual TexSubImage3D
copies + mipmap regeneration happen in ProcessDirtyUpdates, which WB drives
once per frame via ObjectMeshManager.GenerateMipmaps() from its render loop
(WB GameScene.cs:975, just before the opaque pass). GameScene is the file we
replaced with GameWindow, so the call site was silently dropped — staged
updates only reached the GPU as a side effect of PBO growth (UpdateLayerInternal
flushes pending updates before orphaning the PBO). Every layer staged after an
array's LAST growth kept undefined TexStorage3D content behind a valid,
resident bindless sampler handle: white/garbage walls, zh==0, dat tripwires
silent — exactly the #105 signature. Only ObjectRenderBatch.BindlessTextureHandle
consumers are affected (EnvCellRenderer cell shells = indoor walls); entities
resolve via TextureCache (immediate TexImage2D) and terrain via TerrainAtlas
(immediate GenerateMipmap), which is why only indoor walls ever struck.
Fix: WbMeshAdapter.Tick() now calls _meshManager.GenerateMipmaps() after the
staged-upload drain — Tick runs before all draw passes (GameWindow OnRender),
the exact WB-equivalent position.
Evidence (ACDREAM_PROBE_TEXFLUSH=1 apparatus, kept env-gated):
- pre-fix (texflush-prefix.log): pending updates climb 0->48->...->142 and
park at 126 across 34/34 atlas arrays at standstill, forever (19 heartbeats);
brief dips only at PBO-growth crossings — the broken contract live.
- post-fix (texflush-postfix.log): every line after=0 — staged updates drain
the same frame, all 34 arrays clean.
Intermittency explained: background decode-completion order shuffles which
textures land in the never-flushed tail; whether a visible wall samples one is
per-run luck. Also explains the #110 correlation: znear=0.1 makes close-up
geometry newly visible -> more prepare/upload pressure indoors -> bigger tail
-> higher strike probability. The near plane is mechanism-innocent (re-land
follows as its own commit).
Baseline maintained: App 223 / UI 420 / Net 294 / Core 1377 green + 4
pre-existing #99-era failures + 1 skip; CornerFloodReplayTests (5) and
CameraCornerSealReplayTests (2) gates green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bisect (user-gated): two consecutive runs on 0.1 lost indoor textures; the 1.0 bisect run rendered clean. #105 tripwires silent on the bad runs (GL-side). No known mechanism links the near plane to texturing - #110 filed to investigate (RenderDoc / flip-testing) before re-landing retail's znear=0.1, which the corner see-through fix depends on. Comments on all four cameras point at #110 so the retail value is not re-landed blind.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The collided camera eye sits 0.3m from walls (viewer_sphere radius); a 1.0m near plane clipped the wall face away, so pressing the camera into a corner showed the clear color through the wall (gate result: unchanged by the flood fix - it was never a flood bug). Retail sets Render::znear = 0.1 flat in SetFOVRad (decomp :342173, initializer :1101867). All four cameras aligned. Also files #107 (indoor spawn wedge, 3-for-3), #108 (cellar-up terrain sweep across door opening), #109 (exit-door texture/background oscillation) from the 2026-06-10 visual gate; gate confirms the dac8f6a flood fix: room-room + indoor-outdoor transitions clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
THE BUG (pinned deterministically by the new CornerFloodReplayTests harness — real
Holtburg cells, captured corner-press scenario): a smooth 2 cm/step monotonic eye
sweep across the 0172↔0173↔0171 doorway produced a NON-monotonic flood — on ~10 of
61 steps the player's room (0172) vanished from the flood entirely or collapsed to
a sub-pixel sliver, taking its downstream chain (016F, the outside view) with it.
Live, those isolated frames are the §4 background strobe: openings/passages flash
the clear color during transitions, and the corner press shows background at the
angles that park the eye near the doorway plane.
TWO root causes, both fixed:
1. ApplyReciprocalClip ran the reciprocal portal polygon through the legacy
divide-first ProjectToNdc + 2D Intersect path, justified by "the reciprocal is
never near the eye." That assumption is exactly false at doorways/corners: the
reciprocal IS the same opening whose plane the eye presses against (2-60 cm).
ProjectToNdc's MinW=0.05 eye-clip + side-plane clip + divide is knife-edge
there — 2 cm eye moves flipped its output between a no-op and a duplicated-
vertex hairline that ground the healthy region down to <3 distinct vertices.
FIX: route the reciprocal through the SAME homogeneous pipeline as the forward
clip (ProjectToClip + ClipToRegion) — which is what retail does:
PView::OtherPortalClip (decomp:433524-433563) runs the reciprocal through the
very same GetClip(finish=1) → ACRender::polyClipFinish homogeneous clipper.
Also ported retail's skip: exact_match portals (CCellPortal.exact_match,
acclient.h:32300; PView::ClipPortals :433689) bypass the reciprocal clip —
both sides share the same polygon, so re-clipping is redundant.
2. CellView.CanonicalKey missed COLLINEAR re-emissions: the homogeneous region
clipper legitimately inserts intersection vertices ON a subject edge when a
region edge grazes it, so BFS re-clip rounds re-emit the SAME geometric region
with 1-2 extra collinear edge vertices — keyed as distinct, defeating the
dedup and accumulating duplicate polygons (this was the real mechanism behind
the historical "float drift defeats the dedup" rationale that had parked the
reciprocal on the unstable path). FIX: canonicalize away collinear snapped
points (exact integer cross-products on the 1e-3 NDC grid) so the key is
purely a function of the region's corners.
Conformance: CornerSweep_FloodIsCompleteAndMonotone pins the fixed behavior —
61-step monotonic eye sweep ⇒ full flood every step, outside view always reached,
player-room region monotone (was: clean shrink 4.000→2.879 with zero drops, vs
~10 glitch steps before). Diagnostic facts (trace diff, hop microscope, primitive
scratch) retained as the apparatus.
Suites: App 223 green (incl. Build_AppliesReciprocalOtherPortalClip, now passing
with proper tightening AND dedup), Core 1377 green + the 4 pre-existing #99-era
failures + 1 skip, UI 420, Net 294. Visual gate pending: corner press, room↔room,
cellar↔floor, indoor↔outdoor transitions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replays the captured corner/exit "escape" sweeps (corner-seal-capture.log) through
the real ResolveWithTransition with the camera probe call shape. Geometry-map
diagnostic proves every zero-contact traversal runs through a REAL opening: the
0170 exit-door portal for the viewer-outdoor frames; the 0171↔0173↔0172 doorway
chain (0173 = 20 cm threshold cell) for the corner-press frames. Captured eyes land
inside door-opening rectangles; the containment walk shows no path point in solid
cell volume; 8,703/14,230 indoor sweeps in the same capture collided correctly
(pull-in up to 2.77 m) — camera collision works, nothing penetrates.
The user-visible "background at the corner" is therefore NOT collision — it is the
§2a edge-on portal-clip collapse with the eye hovering at the doorway plane. Both
§4 siblings converge on one render-side mechanism: edge-on clip collapse near
opening planes. Next per the 2026-06-08 handoff §5.3: read retail's edge-on clip
oracle (PView::GetClip :432344, PView::ClipPortals :433572, polyClipFinish :702749)
before designing the fix.
Assertion fact = characterization pinning the verified behavior (openings pass
clean); Diagnostic fact = dispatch trace + room map + containment apparatus.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ROOT CAUSE (pinned by the [clip-route] probe run + [gl-state] tripwire, one
capture): EnvCellRenderer.RenderModernMDIInternal established the Transparent
pass state (Enable(Blend) + DepthMask(false)) BEFORE the batch pass-filter, so
a cell whose batches are ALL opaque (a plain cottage interior) hit the
totalDraws==0 early-out and returned without ever reaching the end-of-pass
restore. The frame ended with dmask=0 + blend=1; the NEXT frame's
glClear(GL_DEPTH_BUFFER_BIT) silently no-oped (depth clears honor glDepthMask),
and every world fragment — terrain, entities, player, sky — failed GL_LESS
against its own previous-frame depth ghost. Screen = the fog-tinted clear
color. Onset locks to the building-flood merge because that is the first frame
the flooded building shell draws; holds while merged (the leak re-arms every
frame); camera rotation recovers because the cell drops from the flood and the
restore-skipping path stops running.
Capture evidence (flap-cliproute-capture.log): all three draw-input suspects
exonerated — landscape scissor full-screen all run, terrain-UBO/region-SSBO
planes full-screen on both sides of every merge, all 41,373 instances on the
correct repacked slot with cullEnt=0 — while [gl-state] showed frames entering
with dmask=0 blend=1 for exactly the merged stretches (145,238 consecutive
frames in the held window, flipping with each merge boundary at the end-of-run
strobe cycles).
Fix (all paths root-cause, no suppression):
- EnvCellRenderer: move the pass-state establish BELOW the totalDraws==0
early-out so state is only set on a path that always reaches the restore;
hoist the globalVao==0 check (the second leak-shaped early-out) above the
state set.
- GameWindow frame clear: assert DepthMask(true) before glClear — the clear
DEPENDS on the depth write mask, so it sets the state it depends on
(feedback_render_self_contained_gl_state; this is the 4th instance of the
class, in the same function as the 1st).
Very likely the same family as the "parts of the screen flash while running
past cottages" and cottage enter/exit artifacts (every brief merge = a
1-frame no-op depth clear). Visual gate pending.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>