Commit graph

18 commits

Author SHA1 Message Date
Erik
c4464739d2 #121: dynamics-owner particle pass - world portals visible again; re-gate ledger in ISSUES
Fix: dynamics' ATTACHED emitters (portal swirls on server-spawned portal
entities, creature effects) fell through EVERY particle filter under the
unified pview path - the landscape slice filter carries outdoor statics
(+ the #118 outside-stage dynamics), the per-cell callback carries cell
statics, and T4 deleted the clipRoot==null global pass from normal
frames. T5 never checked portals; the user's re-gate caught it ("all
portals that were previously showing are now gone"). DrawDynamicsLast
now hands its cone-surviving dynamics (minus outside-stage entities,
whose emitters already drew in the landscape slice - alpha particles
must not double-draw) to a new DrawDynamicsParticles callback;
GameWindow draws Scene-pass emitters filtered to those owner ids,
mirroring DrawRetailPViewCellParticles. Retail shape: emitters draw
with their owner object.

Re-gate ledger (user verdicts are axioms):
- #117 CLOSED ("Yes solved"), #118 CLOSED ("Yes solved" + NPC-through-
  door "Yes fixed").
- #108 REOPENED narrowed: cellar-ascent eye-below-grade window only
  (grass covers the exit door until the head pops over ground level);
  fix belongs on the membership/viewer side - the depth-gated punch
  stays (DO-NOT-RETRY).
- #119 user split: phantom walkable stairs at the hill cottage (#113
  family), tower missing stairs + barrel (#119 proper), hill-house
  transparent-on-entry (#112 - re-check after the #120 fix; the
  ping-pong fired at exactly A9B3 0103/010F).
- #120 FIXED pending re-gate (dede7e4).
- NEW #122 window oscillation on entry (re-check after #120 first),
  NEW #123 buildings transiently disappear running close past,
  NEW #124 far-building back walls missing through openings (lead:
  per-building look-in floods run only for outdoor roots -
  NearbyBuildingCells is null for interior roots; retail runs the
  look-in inside LScape::draw for ANY root).

Suites: App 236, Core 1419+2skip, UI 420, Net 294.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 17:36:58 +02:00
Erik
5a80a2ee24 #118: outdoor dynamics draw in the outside stage under interior roots - the house-exit clip+vanish was the SEAL z-killing the player
Root cause (pinned by the new deterministic exit-walk harness, NOT guessed):
under an interior render root, the exit-portal SEAL stamps the door fan at
TRUE depth after the gated full depth clear, and T1's "ALL dynamics last"
pass then drew the outdoor-classified player depth-tested - every fragment
beyond the door plane z-failed against the seal across the whole aperture.
Harness measured the full window: from the moment the sphere center crosses
the plane until the eye follows (~2.6 m of camera lag, ~2.2 s at walk speed)
the player is invisible; while straddling, the beyond-plane body half clips
at the plane. The handoff's three cone-level candidates are all EXONERATED:
the cone walk passes every step; (eye, ViewerCellId) come from the same
SweepEye call with camera-update-before-visibility-read in the same frame;
the side-test window is sub-epsilon under healthy resolution.

Retail oracle (grep-named-first): PView::DrawCells 0x005a4840 runs
LScape::draw FIRST (pc:432719), then the gated depth clear (pc:432731-32)
and the exit-portal seals (pc:432785-86); outdoor cell objects draw inside
the landscape stage (DrawBlock 0x005a17c0 -> DrawSortCell pc:430124), and
an object draws once per overlapped shadow cell (pc:430056-64) - the
straddling body composes from both stages, neither half clips.

Fix: RetailPViewRenderer assigns dynamics to the OUTSIDE stage under an
interior root when outdoor-classified OR sphere-straddling an exit-portal
plane of their flood-visible cell (DynamicDrawsInOutsideStage - pure, the
harness drives it as the ordering contract); they ride the landscape slice
draw (pre-clear, seal-protected) with the same per-slice cone test as
outdoor statics. Indoor dynamics keep the last pass (retail loop C);
straddlers draw in both (retail shadow dual-draw). Outdoor roots keep
all-dynamics-last - the BR-2 punch-after-dynamics lesson (88be519) stands.

Apparatus: HouseExitWalkReplayTests - dat-backed corner-building exit walk
driving the production stack headlessly (RetailChaseCamera damping ->
healthy-sweep viewer resolution -> PortalVisibilityBuilder.Build ->
ClipFrameAssembler -> ViewconeCuller -> the DrawDynamicsLast predicate +
a CPU seal-depth model). 5 tests: cone pin, seal-depth pin, straddle
dual-draw pin, per-step table, stale-root window quantifier (#118 cand 2).

Suites: App 232 (227+5), Core 1416+2skip, UI 420, Net 294.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:49:29 +02:00
Erik
4a307d33b5 T4 (BR-6): ONE visibility gate - ACME BFS deleted from the frame, legacy second render path deleted
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>
2026-06-11 13:03:06 +02:00
Erik
a6aec8c32f T3 (BR-5): viewconeCheck port - per-view sphere culling for statics/dynamics/particles, weather player-gate, unattached outdoor emitters
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>
2026-06-11 12:56:48 +02:00
Erik
88f3ce1fa0 T2 slice 3 (BR-4): draw-driven flood gating - per-building frustum pre-gate, 48m seed cap DELETED
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>
2026-06-11 12:46:46 +02:00
Erik
cf8a2c379b T2 slice 1 (BR-4): multi-view UNION merge + retail 1-px vertex merge (the fixpoint floor)
(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>
2026-06-11 11:20:12 +02:00
Erik
579c8b06bc T1 (fused BR-2/3): retail frame order - dynamics last, punch+seal, shell chop deleted
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>
2026-06-11 11:16:27 +02:00
Erik
88be519ec0 revert(render): BR-2 depth discipline - the gate proved #108 is MEMBERSHIP, not depth
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>
2026-06-11 10:35:57 +02:00
Erik
6d4cac2418 BR-2 commit 1: exit-portal depth SEALS + retail full depth clear (the #108 machinery)
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>
2026-06-11 08:03:10 +02:00
Erik
6cba95047c BR-2 task 1: phantom-site probe (ACDREAM_PROBE_PHANTOM)
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>
2026-06-11 06:27:44 +02:00
Erik
9ce335eb17 fix(render): #114 - scope the PView shell clip to outdoor-eye roots (first user gate findings)
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>
2026-06-10 20:22:29 +02:00
Erik
927fd8fde2 fix(render): #113 - enable GL clip distances for the PView shell pass (phantom exterior staircase)
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>
2026-06-10 16:26:55 +02:00
Erik
682cba36f1 diag(render): §4 flap [clip-route] probe — slot routing + clip-buffer content + landscape scissor
The decisive probe between the two surviving suspects from the 2026-06-09
building-flood-merge handoff (docs/research/2026-06-09-flap-outdoor-fullworld-
building-flood-merge-handoff.md section 1), gated by ACDREAM_PROBE_CLIPROUTE=1,
all print-on-change:

- [clip-route] (RetailPViewRenderer.DrawLandscapeThroughOutsideView): the
  outside slice slot + NDC AABB + planes, the CellIdToSlot routing table, the
  region-SSBO bytes DECODED at the routed slot, and the terrain-UBO head —
  captured after SetTerrainClip + UploadClipFrame + SetClipRouting, i.e.
  exactly what the landscape draws consume. Pins/refutes suspect (b) and the
  slot-repack half of suspect (a).
- [clip-route-disp] (WbDrawDispatcher.Draw, routed draws only): per-slot
  instance histogram exactly as staged for binding=3 plus the count of
  entities dropped by ResolveSlotForFrame CULL. Pins/refutes the
  instance-routing half of suspect (a).
- [clip-route-scis] (GameWindow.DrawRetailPViewLandscapeSlice): the ACTUAL GL
  scissor enable + box read back right after BeginDoorwayScissor — the whole
  landscape pass (sky + terrain + outdoor entities + player) draws inside this
  box, so a doorway-sized box here IS the full-world kill by construction.

Code-reading findings recorded while building the probe: the landscape pass is
scissored to slice.NdcAabb end-to-end (GameWindow.cs DrawRetailPViewLandscapeSlice),
and ResolveEntitySlot CULLs server entities with null ParentCellId while routing
is active — both now directly observable under the probe.

Throwaway apparatus — strip once §4 ships.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 08:37:09 +02:00
Erik
2ec189c106 fix(render): R-A2 seam fix — flood null-BuildingId cells instead of dropping them
MergeNearbyBuildingFloods skipped cells whose BuildingId is null; the pre-R-A2 outdoor-node reverse-portal flood reached them, so dropping left holes at building/terrain seams. Key by (BuildingId ?? CellId) so unstamped/outdoor-adjacent exit-portal cells still seed a per-entrance flood; cells without an exit portal contribute nothing as before. App Rendering 207/207.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:08:14 +02:00
Erik
c62663d7cb feat(render): R-A2 — per-building floods (the flap fix)
Replace the outdoor root's single unified reverse-portal flood (whose root-level
portal-side test oscillated as the chase eye grazed a doorway — the measured
flood 2<->6) with retail's per-building floods.

- OutdoorCellNode.Build(uint): portal-less land root; floods only itself ->
  full-screen OutsideView -> terrain (PortalVisibilityBuilder IsOutdoorNode seed).
- PortalVisibilityBuilder.ConstructViewBuilding: per-building flood seeded at a
  building's own finite entrance (retail ConstructView(CBldPortal) 0x5a59a0 via
  DrawPortal 0x5a5ab0 / portal_draw_portals_only 0x53d870). Entrance-bounded ->
  consistent ~2-cell depth (measured retail cell_draw_num, handoff OPTION-A 3.4).
- RetailPViewRenderer.DrawInside: when the root is the outdoor node, group nearby
  cells by BuildingId and merge each per-building flood into the frame before
  assembly; existing shells/object-list draw path unchanged. 48 m seed cutoff.
- GameWindow: pass flat NearbyBuildingCells only on outdoor-node frames.

Tests: +3 PortalVisibilityRobustnessTests (per-building touches ~2 cells, membership
stable under the measured 36 um eye jitter). UnifiedFloodTests retired (its subject,
the unified flood from the outdoor node, is removed); surviving full-screen-OutsideView
coverage moved to OutdoorCellNodeTests. App Rendering 207/207, Core movement 14/14.

Conformance-verified sound; the grazing-doorway flap is the visual acceptance test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 18:44:43 +02:00
Erik
774cb22713 Revert "fix(render): outdoor look-in draws interior cells only through real doorway apertures (no see-through walls)"
This reverts commit 0030dacaaa.
2026-06-07 21:16:11 +02:00
Erik
0030dacaaa fix(render): outdoor look-in draws interior cells only through real doorway apertures (no see-through walls)
Cutover-flip follow-up: see-through buildings from outside. When the outdoor-node flood reaches a building, each interior cell is meant to draw clipped to its doorway aperture. But DrawEnvCellShells falls back to the no-clip slot 0 (full-screen) when a cell's aperture degenerates — screen-covering when you get close, or edge-on. Indoors that fallback is load-bearing (it seals the room the camera stands in; near walls hide the over-draw). From OUTSIDE it paints the building interior across the whole screen, depth-tested, so it shows wherever the solid exterior does not cover — the see-through walls, appearing 'past a threshold' exactly where the aperture degenerates.

Fix: for the outdoor-node root only, skip a flooded interior cell with no real plane-clip slot (HasRealClipSlot). From outside, 'no real aperture' means 'do not paint this interior', not 'paint it everywhere'. Interior roots keep the seal-everything slot-0 fallback unchanged. Applied to DrawEnvCellShells AND DrawCellObjectLists so a skipped cell shows neither walls nor furniture; the dead DrawPortal exterior look-in gets the same gate.

Root cause traced over the WB EnvCell render path: CellMesh.cs is physics-only; ObjectMeshManager.PrepareCellStructMeshData builds double-sided walls, so this was never a culling bug. App 216/0, build green. Visual gate pending.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:08:43 +02:00
Erik
1405dd8e90 feat(render): indoor render WORKS — terminating portal flood + every-cell seal + look-in FPS
Checkpoint of the unified retail-faithful indoor render. The two-week HANG/grey is fixed and the
interior seals (live-verified by the user). Commits the session render-rewrite foundation together
with the fixes that made it functional.

- HANG fix: PortalVisibilityBuilder.Build portal flood did not terminate (the faithful ProjectToClip
  near-side clip drifts per round, defeating the CellView dedup; the BFS had no bound after U.2a removed
  MaxReprocessPerCell). Fix = drift-tolerant snapped/canonical CellView.Add dedup (PortalView.cs) plus
  restored MaxReprocessPerCell=16 bounded re-enqueue (PortalVisibilityBuilder.cs). Re-enqueue is kept
  (load-bearing for late-slice propagation, Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit);
  only its count is capped. CellViewDedupTests added.
- Seal (DrawCells Task 2): RetailPViewRenderer.DrawEnvCellShells draws EVERY visible cell via
  IndoorDrawPlan.ShellPass (was gated on the ClipFrameAssembler slot filter, leaving slot-less cells grey).
- Look-in FPS: GameWindow exterior look-in candidates limited to the player landblock +-1 (was all ~81
  loaded LBs iterated every outdoor frame). No behaviour change (far cells were >48m, already culled).

Remaining dominant issue = the FLAP at transitions: viewer-cell metastability (render roots at the
camera-eye cell, which oscillates outdoor-indoor as the 3rd-person boom drifts across the doorway,
confirmed in render-sig). SEPARATE fix, NOT the DrawCells port. Full handoff + flap fix plan + tracked
follow-ups (#78 terrain, look-in-from-inside, look-in FPS, L-spotlight):
docs/research/2026-06-07-indoor-render-session-handoff.md.

Baselines: build 0 err; App.Tests 210/210; Core.Tests 1331 pass / 4 fail (pre-existing) / 1 skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:14:43 +02:00