fix #176: light pool tracked the camera via flood scoping - collect residents, anchor at player
The seam-floor purple flicker was NOT a draw z-fight. The in-engine
[seam-*] probe (ACDREAM_PROBE_SEAMDRAW - built because RenderDoc cannot
capture this pipeline: it hides GL_ARB_bindless_texture and the
mandatory-modern startup gate throws; AMD GPU rules out Nsight) killed
every double-draw suspect: ONE shell instance per seam cell at the
lifted z, no floor-coincident entity (portal entities sit at z=-12.05),
zero portal depth fans in the sealed Hub. What it caught instead: the
corridor floor's applied light set flipping wholesale with the flood.
Root cause: c500912b scoped BuildPointLightSnapshot by the per-frame
portal flood, on the research doc's gloss of CEnvCell::visible_cell_table
as "the portal-flood visible set". The named decomp refutes the gloss:
add_visible_cell (0x0052de40) DBObj-LOADS absent cells and inserts them;
a cell activation adds itself + its whole dat visible-cell list
(0x0052e228/0x0052e24a); entries leave only via the flush machinery.
It is the RESIDENT-cell registry - gaze can never remove a cell.
add_dynamic_lights (0x0052d410) walks the WHOLE table per frame
(caller 0x00452d30), and insert_light (0x0054d1b0) caps the pool by
distance to Render::player_pos (0x0054d1dd). Retail's pool is a function
of player position only. Ours followed the camera: turning changed the
flood (probe: 8..41 cells across one turn), the six intensity-100
under-room portal purples entered/left the pool, and the wedge blinked.
Fix: BuildPointLightSnapshot(playerWorldPos) collects ALL registered
(=resident) lit lights; over cap keeps dynamics FIRST (retail's separate
7-slot dynamic pool never competes with statics) then nearest-the-player;
the RebuildScopedLights callback is deleted. Live-verified with the probe:
full-circle turn, flood churning 8..41, the floor set held the same 8
identities on every post-spawn frame. The purple wedge SHAPE stays - it
is cdb-proven retail-faithful.
Residual deviation (AP-85 rewritten): single 128 pool vs retail's
7-dynamic/40-static degrade-scaled dual pools - the Hub now shows
7 purples + viewer where retail's cdb showed 4 + viewer + fixture slots;
if the gate reads the wedge as too purple, the A7 dual-pool cap is the
faithful trim.
Pins: PointSnapshot_HubScaleLightCount_ObjectSelectionIsCameraInvariant
(rewritten to the corrected model),
PointSnapshot_OverCap_DynamicsNeverEvictedByNearerStatics,
PointSnapshot_OverCap_KeepsNearestThePlayer,
PointSnapshot_ResidentCollection_CellTagDoesNotFilter.
Suites: Core 2599+2skip / App 726+2skip / UI 425 / Net 385.
The [seam-*] probes stay until the visual gate passes, then strip.
Correction banner added to 2026-07-06-a7-per-cell-lighting-pseudocode.md;
outcome banner on the z-fight handoff; ISSUES #176 updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8cb3176daa
commit
d8984e877f
12 changed files with 525 additions and 194 deletions
|
|
@ -149,21 +149,37 @@ the connecting opening, and stays rendered through the full descent.
|
|||
|
||||
## #176 — Purple flashing on dungeon floors at cell seams, camera-angle dependent
|
||||
|
||||
**⚠️ UPDATE 2026-07-06 (final — cdb-confirmed, RenderDoc handoff) — it's a RUNTIME draw
|
||||
z-fight, NOT lighting.** Full elimination table + plan:
|
||||
`docs/research/2026-07-06-176-seam-floor-zfight-handoff.md`. The purple WEDGE SHAPE is
|
||||
retail-FAITHFUL — cdb (`tools/cdb/issue176-floor-light.cdb`) proved retail applies the same
|
||||
4 intensity-100 magenta portal lights + the viewer fill to EVERY Hub cell via D3D hardware,
|
||||
so the faceted per-vertex purple is exactly what retail shows. The only defect is the
|
||||
FLICKER, eliminated as: NOT lighting (per-light cap + all-dynamic-per-cell both no-change),
|
||||
NOT membership (render cell `0x8A020164` stable on 100% of 188k frames / 526 angles), NOT dat
|
||||
geometry (coplanar sweep empty at the z=−6 corridor floor, incl. cell `0164`). → a RUNTIME
|
||||
double-draw of the seam floor. Suspects: the `+0.02 m` shell lift (AP-32/#130), the #178
|
||||
double-sided cell-shell stopgap, a portal depth-mask. **NEXT: RenderDoc pixel-history** on a
|
||||
flickering floor pixel to name the two fighting draws. Shipped this arc (retail-faithful, NOT
|
||||
the fix): scoping `c500912b` + `SelectForCell` (all-dynamic-lights-per-cell).
|
||||
**🟡 FIX SHIPPED 2026-07-06 (pending user visual gate) — the flicker was the LIGHT POOL
|
||||
tracking the CAMERA, not a draw z-fight.** The z-fight framing was refuted by the in-engine
|
||||
`[seam-*]` probe (RenderDoc is infeasible on this pipeline — it hides
|
||||
`GL_ARB_bindless_texture` and our mandatory-modern startup gate throws; AMD GPU rules out
|
||||
Nsight): exactly ONE shell instance per seam cell at the lifted z (−5.98), NO
|
||||
floor-coincident entity (portal entities sit at z=−12.05, six meters down), ZERO portal
|
||||
depth fans (sealed dungeon). What the probe DID catch: the corridor floor's applied light
|
||||
set flipping wholesale with flood composition. Root cause (named-decomp-verified): the
|
||||
`c500912b` scoping port glossed retail's `CEnvCell::visible_cell_table` as "the portal-flood
|
||||
visible set" — it is the **RESIDENT-cell registry** (`add_visible_cell` 0x0052de40
|
||||
dat-loads absent cells; populated from each activated cell + its dat visible-cell list;
|
||||
gaze can never remove entries), and `Render::insert_light` (0x0054d1b0) caps the pool
|
||||
nearest **`Render::player_pos`**, not the camera. Our flood-scoped pool dropped/admitted
|
||||
the six intensity-100 under-room portal purples as the camera turn changed the flood
|
||||
(probe: flood churned 8→41 cells over a full turn) → the wedge blinked. **Fix:**
|
||||
`BuildPointLightSnapshot(playerWorldPos)` — resident collection (all registered lit
|
||||
lights), dynamics-first player-nearest cap; the `RebuildScopedLights` callback deleted.
|
||||
Verified live: full-circle turn sweep, flood churning 8→41, the floor's set held the SAME
|
||||
8 identities on every post-spawn frame. Pins: `PointSnapshot_HubScale…CameraInvariant` (rewritten),
|
||||
`PointSnapshot_OverCap_DynamicsNeverEvictedByNearerStatics`, `PointSnapshot_ResidentCollection_CellTagDoesNotFilter`.
|
||||
Register AP-85 rewritten; correction banner in
|
||||
`docs/research/2026-07-06-a7-per-cell-lighting-pseudocode.md`. Residual parity note for the
|
||||
gate: our single 128 pool admits 7 purples + viewer (8 dynamics, all 8 slots) where retail's
|
||||
7-dynamic/40-static dual pool showed 4 purples + viewer + fixture slots — if the wedge reads
|
||||
"too purple," the A7-arc dual-pool cap is the faithful trim. `[seam-*]` probes stay until the
|
||||
gate passes, then strip.
|
||||
|
||||
**Status:** OPEN
|
||||
**Gate:** stand in the `0x8A020164` corridor, turn back-and-forth across the seam — the
|
||||
purple wedge must hold steady (its faceted SHAPE is retail-correct and stays).
|
||||
|
||||
**Status:** 🟡 FIX SHIPPED — pending user gate
|
||||
**Severity:** MEDIUM (visible artifact along every corridor seam in the M1.5 dungeon)
|
||||
**Filed:** 2026-07-06
|
||||
**Component:** render — floor-portal polygons / portal surface state
|
||||
|
|
|
|||
|
|
@ -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 | **⚠️ 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-85 | **Point-light pool = single 128-cap player-nearest list vs retail's dual pools (7 dynamic + 40 static, degrade-scaled)** (#176 CORRECTED AGAIN 2026-07-06, second correction same day): the pool is now retail's shape — collected per frame from ALL registered (=resident-cell) lit lights and, over cap, kept dynamics-FIRST then nearest-THE-PLAYER (`Render::insert_light` 0x0054d1b0 sorts by `Render::player_pos`; `CEnvCell::add_dynamic_lights` 0x0052d410 walks the WHOLE static `CEnvCell::visible_cell_table`, which `add_visible_cell` 0x0052de40 proves is the RESIDENT-cell registry — it DBObj-loads absent cells; NOT the portal flood). **Two prior camera-coupled models were each a #176 flicker mechanism and are deleted: (1) nearest-CAMERA-128 (chase-boom churn), (2) frame-FLOOD scoping `c500912b` (the prior AP-85 text called it faithful — WRONG: the doc glossed `visible_cell_table` as "the portal-flood visible set"; the under-room portal purples entered/left the pool as the camera turned = the seam blink, probe-proven `[seam-blk]`: flood churned 8→41 cells over a full turn while the fixed pool held the same 8 identities).** Remaining deviation: ONE pool capped 128 vs retail's separate 7-dynamic/40-static with degrade scaling (`0x0081ec94/98`, `SetDegradeLevelInternal` 0x0054c3c0) — in the Hub this admits 7 purples + viewer (8 dynamics) where retail's 7-dynamic cap + static slots would mix in warm fixture light (cdb saw 4 purples + viewer) | `src/AcDream.Core/Lighting/LightManager.cs` (`BuildPointLightSnapshot`, `MaxGlobalLights`); pins `PointSnapshot_HubScaleLightCount_ObjectSelectionIsCameraInvariant`, `PointSnapshot_OverCap_DynamicsNeverEvictedByNearerStatics`, `PointSnapshot_ResidentCollection_CellTagDoesNotFilter` | Single-pool is the minimal faithful-in-effect shape: dynamics-first ordering reproduces the never-evicted property; 128 > 40+7 only ever admits EXTRA far statics (out of selection range anyway) | A room with >7 resident dynamics shows them all (retail trims to 7 player-nearest) — slightly purpler Hub wedge than retail; adopt the dual pools + degrade caps in the A7 arc | `insert_light` 0x0054d1b0 (player-sorted, capped); `add_visible_cell` 0x0052de40 (resident registry); `add_dynamic_lights` 0x0052d410 (whole-table walk); caller 0x00452d30; `calc_point_light` 0x0059c8b0 (static 1/d³ curve — A7 fix #2) |
|
||||
| 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` |
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,26 @@
|
|||
# Pickup handoff — #176 seam-floor lighting flicker = a RUNTIME draw z-fight (RenderDoc next)
|
||||
|
||||
> ## ✅ OUTCOME (2026-07-06, the pickup session) — z-fight REFUTED; it was the light POOL tracking the camera; FIX SHIPPED
|
||||
>
|
||||
> RenderDoc was infeasible (it does not support `GL_ARB_bindless_texture` and hides
|
||||
> it → our mandatory-modern startup gate throws `NotSupportedException`; the AMD RX
|
||||
> 9070 XT rules out Nsight). The equivalent evidence came from the in-engine
|
||||
> `ACDREAM_PROBE_SEAMDRAW` probe (`[seam-blk]/[seam-cell]/[seam-snap]/[seam-ent]/
|
||||
> [seam-mask]`): **one** shell instance per seam cell at the lifted z (suspect 1
|
||||
> dead), **no** floor-coincident entity (suspect-class dead), **zero** portal depth
|
||||
> fans (suspect 3 dead) — but the corridor floor's applied LIGHT SET flipped
|
||||
> wholesale with flood composition. Root cause: the `c500912b` visible-cell scoping
|
||||
> was built on a wrong gloss — retail's `CEnvCell::visible_cell_table` is the
|
||||
> **RESIDENT-cell registry** (`add_visible_cell` 0x0052de40 dat-loads absent cells),
|
||||
> not the frame flood, and `insert_light` (0x0054d1b0) anchors the pool at
|
||||
> **`Render::player_pos`**. Fix: `BuildPointLightSnapshot(playerWorldPos)` — resident
|
||||
> collection, dynamics-first player-nearest cap; `RebuildScopedLights` deleted.
|
||||
> Live-verified: flood churned 8→41 cells over a full-circle turn while the floor's
|
||||
> set held the same 8 identities. Details: ISSUES #176, register AP-85, the
|
||||
> correction banner in `2026-07-06-a7-per-cell-lighting-pseudocode.md`. The document
|
||||
> below is kept as the historical elimination record — its DO-NOT-RETRY table
|
||||
> remains valid (all those channels stay dead).
|
||||
|
||||
**Paste the companion prompt below into a fresh session.** Read this file, then
|
||||
`claude-memory/project_render_pipeline_digest.md` (top banner), then **ISSUES #176**.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,40 @@
|
|||
# A7 dungeon lighting — retail per-cell light model (source-confirmed pseudocode)
|
||||
|
||||
> ## ⚠️ CORRECTION #2 (2026-07-06, later the same day — the #176 flicker root cause)
|
||||
>
|
||||
> **§1.3's gloss is WRONG: `CEnvCell::visible_cell_table` is NOT "the portal-flood
|
||||
> visible set".** It is a **static class member** — `HashTable<uint, CEnvCell*>
|
||||
> CEnvCell::visible_cell_table` (defined 0x0081dd28, ctor 0x006fea4a) — the
|
||||
> **RESIDENT-EnvCell registry**. Proof from the named decomp:
|
||||
> - `CEnvCell::add_visible_cell(id)` (0x0052de40) returns the cell if present,
|
||||
> otherwise **loads it from the dat** (`DBObj::Get(QDID(id, 3))`) and inserts it.
|
||||
> Entries leave only via the flush machinery (`cell_flush_table` /
|
||||
> `flush_cells` 0x0052def0) — **camera gaze can never remove a cell**.
|
||||
> - It is populated at cell activation: a cell adds **itself + its entire dat
|
||||
> visible-cell list** (`add_visible_cell(this->m_DID.id)` 0x0052e228 +
|
||||
> `add_visible_cell(this->stab_list[i])` loop 0x0052e24a).
|
||||
> - `CEnvCell::add_dynamic_lights` (0x0052d410) walks the **whole table**
|
||||
> (`begin(&visible_cell_table)`), per frame, from 0x00452d30 (SmartBox).
|
||||
> - `Render::insert_light` (0x0054d1b0) sorts the pool by distance to
|
||||
> **`Render::player_pos`** (0x0054d1dd–0x0054d214; set from
|
||||
> `player->m_position`, SmartBox 0x00453d3a, viewer fallback 0x00455ab6).
|
||||
>
|
||||
> **So retail's pool = all RESIDENT cells' lights, capped nearest-the-PLAYER —
|
||||
> a function of player position only, camera-invariant.** The §3.1 "slice 1"
|
||||
> shipped below (flood-scoped `BuildPointLightSnapshot(camPos, visibleCells)`,
|
||||
> `c500912b`) implemented the wrong gloss and WAS the #176 seam-floor blink: the
|
||||
> under-room portal purples entered/left the pool as the camera turned (probe
|
||||
> `[seam-blk]`: flood churned 8→41 cells across a full turn). Fixed same day —
|
||||
> the scoping + the `RebuildScopedLights` callback are deleted;
|
||||
> `BuildPointLightSnapshot(playerWorldPos)` collects every registered lit light
|
||||
> (registration lifecycle = residency) with dynamics-first player-nearest
|
||||
> capping. §2's "Retail never has 366 candidates" and the through-floor
|
||||
> paragraph are likewise wrong: retail's Hub pool DOES contain the under-room
|
||||
> purples (the cdb capture showed them applied to every Hub cell — the wedge is
|
||||
> faithful). Register row AP-85 rewritten. Lesson (feedback_bn_decomp_field_names
|
||||
> class): a decomp identifier's NAME is not its semantics — verify what
|
||||
> POPULATES a structure before adopting its name as its meaning.
|
||||
|
||||
**Date:** 2026-07-06 (continuation of the #176/#177 arc)
|
||||
**Purpose:** the mandated `grep named → decompile → pseudocode → port` step 3 for
|
||||
the A7 per-cell lighting fix. Captures the RETAIL light-selection model exactly as
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue