feat(lighting): A7 visible-cell light scoping + [indoor-light] probe (NOT the #176/#177 fix)
Port retail's per-frame light collection: the point-light pool is built from ONLY the currently-visible cells' lights, matching CObjCell::add_*_to_global_lights (0x0052b350/0x0052b390) walked over CEnvCell::visible_cell_table (0x0052d410) — not a flat world-space set capped at 128-nearest-camera. - LightSource.CellId (retail insert_light arg6 -> RenderLight +0x6c); tagged at both registration sites from entity.ParentCellId (live weenie fixtures + dat EnvCell statics). - LightManager.BuildPointLightSnapshot(camPos, visibleCells): a light joins the pool iff CellId==0 (viewer/global) or its cell is in the flood. 128 cap kept as a now-non-biting backstop (retail's is 40 static + 7 dynamic, 0x0081ec94/8). - Threaded via RetailPViewDrawContext.RebuildScopedLights, invoked in DrawInside after the flood resolves prepareCells and before the draws (renderers select from the same in-place-rebuilt PointSnapshot; EnvCellRenderer clears its per-cell cache each pass). - [indoor-light] probe (ACDREAM_PROBE_INDOOR_LIGHT=1) dumps the scoped-pool SET COMPOSITION. Un-skips LightManagerTests.PointSnapshot_HubScaleLightCount_ObjectSelectionIsCameraInvariant. CORRECTION: the handoff called the camera-cap the "confirmed" #176/#177 mechanism. The probe PROVES scoping works (291 Hub fixtures -> pool of 1-9, ~285 through-floor lights dropped/frame, CellIds match the flood), but the user's VISUAL GATE showed BOTH symptoms unchanged. So pool composition is NOT the cause. #176 real cause = an over-bright purple point light (intensity=100, color 0.784,0,0.784 -- from [light-detail]); #177 = a portal-visibility miss (stairs not drawn looking back). Both stay OPEN. This change is retail-faithful and retires the camera-eviction latent bug; kept as such, not as the symptom fix. Register AP-85 corrected; ISSUES #176/#177 re-diagnosed; render digest banner updated. Decomp: insert_light 0x0054d1b0, minimize_object_lighting 0x0054d480, calc_point_light 0x0059c8b0; pseudocode docs/research/2026-07-06-a7-per-cell-lighting-pseudocode.md. Suites green: Core 2595 + 2 skip, App 719 + 2 skip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e1746ca10d
commit
c500912bf8
10 changed files with 516 additions and 35 deletions
|
|
@ -179,7 +179,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
| AP-80 | **PlanFromVelocity survives for velocity-only NPC cycles** (M16): UpdatePosition-derived speed picks Ready/Walk/Run cycles for server-controlled creatures whose UMs never arrive (scripted-path NPCs); retail derives every cycle from motion messages through the motion tables (R4-V4 note; pre-existing mechanism, row added per the V4 plan) | `src/AcDream.Core/Physics/ServerControlledLocomotion.cs` (`PlanFromVelocity`); consumer `GameWindow.ApplyServerControlledVelocityCycle` | Some ACE entities move by position updates alone — without this, they slide in T-pose; constants (StopSpeed 0.2, RunThreshold 1.25) tuned against live ACE traffic | Cycle-pick thresholds are acdream inventions — a creature intended to walk fast may show run legs near the threshold | retire in R6 (root motion + full per-tick order) |
|
||||
| AP-81 | **Remote-DR gravity toggled via the Gravity STATE bit**: the jump handler sets `Body.State \|= Gravity` at VectorUpdate and both landing blocks clear it after `HitGround()`; retail keeps GRAVITY set for the object's whole life and gates gravity ACCELERATION on the Contact transient (`calc_acceleration`) (pre-existing K-fix9/K-fix15 mechanism, row added during #161 — which also fixed the ordering so `Motion.HitGround()`'s verbatim `state&0x400` gate runs BEFORE the clear) | `src/AcDream.App/Rendering/GameWindow.cs` (VectorUpdate jump handler + the two landing blocks) | The DR tick integrates gravity only for airborne remotes; the flag dance delivers exactly that without porting the full contact-gated `calc_acceleration` chain; the #161 ordering fix keeps the retail HitGround contract satisfied | Any NEW call into `Motion.HitGround`/`LeaveGround` placed after the clear silently no-ops on the gravity gate (the #161 leg-2 class); grounded remotes carry a non-retail state word (probes comparing state bits vs retail mislead) | `CPhysicsObj::calc_acceleration` (contact-gated); `set_on_walkable` 0x00511310; retire in R6 (contact-gated accel + persistent GRAVITY) |
|
||||
| AP-82 | **StickyManager deep-overlap back-off sign pin**: when the stick-gap overlap exceeds one tick's step (`speed×quantum < \|dist\|`, `dist < 0`), acdream applies `delta = −(speed×quantum)` (rate-limited back-off); ACE's literal port keeps `+delta` there — a runaway that steers INTO the target with equilibrium at centers-coincident. The BN mush (0x00555554-0x00555597) is unreadable on exactly this compare; the pin is refuted-by-evidence against ACE-literal: #171 gate-3 probe showed 1661 deep-overlap ticks all steering inward (monsters converged to centerDist≈0 — "monster inside the player") while retail side-by-side on the same ACE shows separation. ACE servers essentially never reach the branch (quantum ≥1/30 → threshold ~1 m; render-rate quanta → ~0.13 m) | `src/AcDream.Core/Physics/Motion/StickyManager.cs` (`AdjustOffset` delta clamp; conformance `StickyManagerTests.AdjustOffset_DeepOverlap_BacksOff_RateLimited`) | Minimal interpretation consistent with the mush structure AND observed retail; identical to ACE-literal in every shallow/outside case | If retail's true deep-overlap behavior differs (e.g. no movement at all), our back-off rate diverges in that rare state; verify via cdb `StickyManager::adjust_offset` trace with a forced overlap when convenient | `StickyManager::adjust_offset` 0x00555430 (x87 mush); ACE StickyManager.cs:117-121 (the literal branch this pin overrides) |
|
||||
| AP-85 | **Per-frame flat point-light snapshot capped at the 128 lights nearest THE CAMERA** (`BuildPointLightSnapshot`); retail registers lights per-CELL (`insert_light` 0x0054d1b0) and `minimize_object_lighting` (0x0054d480) consults the reaching set with NO global pool cap. The cap BITES in the Facility Hub (366 registered fixtures → 238 evictions/frame) and the eviction is the CONFIRMED mechanism of #176 (purple seam flash — an in-range torch of a visible cell ranks past the cap and drops from that cell's 8-set; per-cell Gouraud pops as the camera moves) + #177 (a stair room's fixtures all past the cap render it 0.2-ambient-dark until approach). ⚠️ Raising to 1024 was live-tested 2026-07-06 and REVERTED: the uncapped pool exposes (a) light-through-solid-floors (no per-cell reach/occlusion — the under-room portal light washes the corridor above), (b) stationary weenie fixtures on the DYNAMIC 1/d falloff (~9× retail's static 1/d³ at 3 m; #143 misassignment for ACE-served fixtures), (c) an unexplained striped floor artifact. Fix = the A7 arc: per-cell light registration + static curve for fixtures + the stripe hunt, THEN uncap | `src/AcDream.Core/Lighting/LightManager.cs` (`MaxGlobalLights` — the load-bearing-stopgap comment); desired-end-state pin (Skip) `LightManagerTests.PointSnapshot_HubScaleLightCount_ObjectSelectionIsCameraInvariant` | The 128 cap keeps the light pool local to the camera, which accidentally APPROXIMATES per-cell reach (far lights can't leak through floors into view) — the least-wrong state until A7 ports real per-cell registration | The #176/#177 pop class stays live until A7 (purple flashes at seams; unlit rooms popping lit on approach); any dungeon with >128 fixtures has camera-dependent per-cell lighting | `minimize_object_lighting` 0x0054d480 (no global pool cap); `insert_light` 0x0054d1b0 (per-cell registration); `calc_point_light` 0x0059c8b0 (static 1/d³ bake curve) |
|
||||
| AP-85 | **⚠️ CORRECTED 2026-07-06: visible-cell scoping SHIPPED (`LightSource.CellId` from `entity.ParentCellId` + `BuildPointLightSnapshot(camPos, visibleCells)` over the portal-flood set; probe-proven to drop ~285 through-floor lights/frame in the Hub; end-state pin un-skipped). The visual gate REFUTED the light-cap #176/#177 attribution stated below — BOTH symptoms were UNCHANGED, so #176 = an intensity-100 purple point light `(0.784,0,0.784)` and #177 = a portal-visibility miss (see digest banner). Residual deviation now: 128 backstop vs retail 40+7 (0x0081ec94/8), no dynamic-priority split — benign (visible-scoped pool is 1–9). HISTORICAL claim below.** **Per-frame flat point-light snapshot capped at the 128 lights nearest THE CAMERA** (`BuildPointLightSnapshot`); retail registers lights per-CELL (`insert_light` 0x0054d1b0) and `minimize_object_lighting` (0x0054d480) consults the reaching set with NO global pool cap. The cap BITES in the Facility Hub (366 registered fixtures → 238 evictions/frame) and the eviction is the CONFIRMED mechanism of #176 (purple seam flash — an in-range torch of a visible cell ranks past the cap and drops from that cell's 8-set; per-cell Gouraud pops as the camera moves) + #177 (a stair room's fixtures all past the cap render it 0.2-ambient-dark until approach). ⚠️ Raising to 1024 was live-tested 2026-07-06 and REVERTED: the uncapped pool exposes (a) light-through-solid-floors (no per-cell reach/occlusion — the under-room portal light washes the corridor above), (b) stationary weenie fixtures on the DYNAMIC 1/d falloff (~9× retail's static 1/d³ at 3 m; #143 misassignment for ACE-served fixtures), (c) an unexplained striped floor artifact. Fix = the A7 arc: per-cell light registration + static curve for fixtures + the stripe hunt, THEN uncap | `src/AcDream.Core/Lighting/LightManager.cs` (`MaxGlobalLights` — the load-bearing-stopgap comment); desired-end-state pin (Skip) `LightManagerTests.PointSnapshot_HubScaleLightCount_ObjectSelectionIsCameraInvariant` | The 128 cap keeps the light pool local to the camera, which accidentally APPROXIMATES per-cell reach (far lights can't leak through floors into view) — the least-wrong state until A7 ports real per-cell registration | The #176/#177 pop class stays live until A7 (purple flashes at seams; unlit rooms popping lit on approach); any dungeon with >128 fixtures has camera-dependent per-cell lighting | `minimize_object_lighting` 0x0054d480 (no global pool cap); `insert_light` 0x0054d1b0 (per-cell registration); `calc_point_light` 0x0059c8b0 (static 1/d³ bake curve) |
|
||||
| AP-84 | **BSP shadow-shape part poses = motion-table default-state frame snapshot at registration, not retail's live CPhysicsPart pose** (#175): server entities with a wire MotionTableId register their BSP part shapes at the default style's first-cycle LowFrame pose (the closed pose for doors — `GameWindow.MotionTableDefaultPose`); retail collision reads each part's CURRENT pose every test. Equivalent for the door lifecycle (closed = default pose; open = ETHEREAL bypasses collision entirely, #150) and for idle statics | `src/AcDream.App/Rendering/GameWindow.cs` (`MotionTableDefaultPose` + the RegisterServerEntityCollision override); `src/AcDream.Core/Physics/ShadowShapeBuilder.cs` (`partPoseOverride`) | Registration is one-shot in acdream (retail re-poses parts per frame); the default-state pose is the correct idle pose and the only non-ethereal pose doors ever collide in | An entity whose server-driven motion state materially MOVES a BSP-bearing part while NON-ethereal would collide at the stale default pose (no known case — doors are the dominant BSP-part weenies); revisit if animated non-ethereal BSP movers appear | `CPhysicsPart` live pose (see #150 notes); motion-table default state = CPartArray init; ShadowShapeBuilder placement-frame fallback for table-less entities |
|
||||
| AP-83 | **CylCollideWithPoint PerfectClip TOI sub-branches decoded via ACE, not the binary**: the CCylSphere family port (2026-07-05, retires AP-6) reads `collide_with_point`'s PerfectClip time-of-impact math (0x0053adb6+) from ACE `CylSphere.CollideWithPoint` because the BN x87 mush is unreadable there; two ACE-verbatim quirks ported as-is (`movement.Z + radius` in the not-definite ascending case; `GlobalCurrCenter[0]` used even for head-sphere hits — the latter matches the raw decomp read). NOT exercised in M1.5: no mover sets PerfectClip (players never do; the non-PerfectClip path — SetCollisionNormal + Collided — is decomp-verified). Separately, the grounded head-sphere slide passes the HEAD disp per retail 0x0053b843 where ACE passes the foot disp — retail wins (ACE bug, not copied) | `src/AcDream.Core/Physics/TransitionTypes.cs` (`CylCollideWithPoint`; pseudocode doc `docs/research/2026-07-05-ccylsphere-collision-family-pseudocode.md` §7-8) | The load-bearing paths (non-PerfectClip Collided; the family's step-up/step-down/land) are decomp-verified; the TOI tail is dead code until missiles arm PerfectClip | If missiles (F.3) arm PerfectClip, the two ACE quirks may diverge from retail — clip-through or wrong deflection on cylinder targets; re-decompile 0x0053acb0 in Ghidra before shipping missiles | `CCylSphere::collide_with_point` 0x0053acb0 (pc:324173, x87 mush from 0x0053adb6); ACE CylSphere.cs `CollideWithPoint` |
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue