Commit graph

298 commits

Author SHA1 Message Date
Erik
5e271dd719 docs: reconcile M1.5 tracking with git reality — G.3 dungeons SHIPPED, not "broken"
The milestones doc, roadmap, CLAUDE.md banner, and #138's ISSUES cross-ref had
drifted ~4 weeks behind shipped work — they still claimed "dungeons don't work
AT ALL / terrain-less landblocks unsupported (#133)", which sent this session
chasing a phantom door "regression" for an hour. Git reality (and tonight's live
dungeon session) refute it:

- #133 teleport-into-dungeon DONE (G.3a hold-until-hydrate FSM 7947d7a/2ce5e5c8/47ae237e);
  the terrain-less premise was refuted at design time (90786c19: dungeon LandBlock
  is flat-terrain, streams via the existing pipeline).
- #95 portal-graph visibility blowup RESOLVED (9.1M→~39k instances, a40c38e8).
- #137 dungeon collision CLOSED (0efa7ed2); doors #187/#188; A7 lighting #79/#93/#80/#154 CLOSED.
- #138 teleport-OUT ESSENTIALLY RESOLVED in code — the remaining M1.5 gap is one
  RECORDED end-to-end round-trip user gate, not broken code.

Also fixes a numbering trap: #138's body cited "#146/#147" for its collision
residuals, but those numbers are D.2b inventory-polish issues in this worktree;
the real residuals are #152 (portal-in walls, DONE 49d743f) + #151 (far-town
walls, FIXED 9743537). Sole live residual now surfaced: far-town teleport-OUT
arrival cascade (#145-residual, REOPENED — capture-harness-first).

Historical working-notes annotated [SUPERSEDED] rather than deleted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 09:45:05 +02:00
Erik
217a4bad69 Merge branch 'main' into claude/peaceful-visvesvaraya-e0a196
# Conflicts:
#	docs/ISSUES.md
#	docs/architecture/retail-divergence-register.md
2026-07-09 23:18:52 +02:00
Erik
0ecb729527 docs: close #192 (login streaming race) — user visual gate passed
"Looks good." Filed directly to Recently closed since the fix already
shipped and gated this session (fa9aedca).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 16:12:31 +02:00
Erik
3dcd53cab7 docs: bulk issues triage — close 27, note 10 investigated-open
User-driven triage pass over ~60 open issues: 19 closed from the
user's own recollection, 8 more closed after this session's parallel
investigation confirmed the underlying bug/task no longer applies
(#96 accepted-divergence reclassification, #60/#68/#126/#4/#81/#33
fixed by commits that never cross-referenced their issue number, #87
superseded by Phase O + Phase A8). 9 issues investigated and confirmed
still genuinely open got a dated triage note appended in place
(#116/#104/#178/#29/#146/#147/#148/#156/#72); #41 got a user-note only.

Two investigated issues (#148, #156) turned up real completed fixes
sitting on an unmerged branch (claude/peaceful-visvesvaraya-e0a196,
11 commits, diverged from main 168 commits ago) — flagged in their
triage notes and separately to the user, not acted on here.

Verified before committing: header count unchanged (200 before/after,
no blocks lost or duplicated), spot-checked 6+ closures across every
category (user-closed, investigation-closed, stayed-open-with-note,
the #87 duplicate-heading edge case) for accuracy against the
underlying investigation evidence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 13:52:41 +02:00
Erik
44cced715c docs: close #93 and #80 (indoor lighting) — user re-verified 2nd-floor fix
"Number 80 is closed, I verified again." Moved #93 (the lighting
umbrella) and #80 (2nd-floor darkness) to Recently closed. #94
(held-item spotlight) is NOT folded into this closure and stays open —
user confirmed acdream doesn't support equipping hand-held items yet,
so it's currently untestable; marked BLOCKED rather than an active A7
target so it doesn't keep resurfacing as unfinished lighting work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 13:17:25 +02:00
Erik
754b299f7a docs: close #189 (fountain water spray) — user visual gate passed
"Fountain is back! We can close it." Root cause was #190 (entity-id
overflow), not the light-carrier hydration fix directly. Moved to
Recently closed with the mechanism summary; the candle-flame
identification sub-thread is noted as an unraised residual rather than
a new open issue, since the user didn't re-flag it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 12:44:13 +02:00
Erik
6e034ac610 docs: A7.L1 session bookkeeping — close #190, update #93/#189, file #191
- #190 (interior entity id overflow) moved to Recently closed with full
  evidence trail — found + fixed same session as #189's investigation.
- #189 (missing particles) updated: root cause was #190's id aliasing,
  not the light-carrier hydration fix itself; fix shipped but the
  fountain's water spray hasn't been re-confirmed visually yet (session
  moved to an FPS question, then a movement bug, before circling back).
  The "candle" identification sub-thread stays open regardless.
- #93 (indoor lighting umbrella) updated earlier this session with the
  two A7.L1 root causes + fixes; #80/#94 still need re-verification
  before it can close.
- #191 filed: tapping W briefly glides forward without playing the step
  animation (retail: single visible step). Different subsystem
  (movement/animation, not rendering) — not investigated this session,
  filed to keep it from interrupting A7 lighting/particle work.
- Roadmap Phase A7 progress note added earlier this session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 12:40:42 +02:00
Erik
5d013dcb10 docs: close #188 (fading-wall doors fade + hold; door flip-back fix, 3284dd0a)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 09:20:51 +02:00
Erik
e2e285b855 fix(#187): drop the "Name == Door" special-case — register any entity with a resolvable MotionTableId
The door-swing animation rescue (GameWindow.cs:3897, for entities whose rest pose
is a static single frame but which still carry a reactive MotionTable) was gated on
an exact display-name string match. Sliding doors, gates, portcullises, and disguised
secret-passage props ("Magic Wall") all fail that check because their in-game Name
isn't literally "Door" -- so ACE's UpdateMotion for them was silently dropped forever
by the _animatedEntities.TryGetValue bail-out in OnLiveUpdateMotion.

Retail's own dispatch chain (ACCObjectMaint::CreateObject -> CPhysicsObj::
set_description -> SetMotionTableID -> CPartArray::SetMotionTableID 0x005186e0 ->
MotionTableManager::PerformMovement) is unconditionally data-driven: the only gate
for creating a motion dispatcher anywhere in that chain is "motion table id != 0" --
no CDoor class, no WeenieType switch, no name check. Production weenie data confirms
Sliding Door / Portcullis / Gate / Magic Wall all carry the identical WeenieType=Door
+ non-zero-MotionTableId shape as a plain "Door", differing only in display name.

Fix: the branch's existing `mtableId != 0` check (already computed one line later)
is now the entire gate, matching retail exactly. IsDoorSpawn deleted (dead code);
IsDoorName kept only for an unrelated diagnostic log-label filter.

Live-verified: sliding doors now animate open/closed correctly. Full regression
green (App 741 / Core 2631).

docs(#188): file the fading-wall render gap surfaced during #187's live gate

A "Pedestal Weak Spot" secret-passage door dispatches correctly (proving #187's fix
reaches it) but never visibly changes. Decoded its actual dat MotionTable directly
(0x090000F9): its open cycle carries EtherealHook + TransparentPartHook +
SoundTableHook -- a translucency-fade effect, not part-transform motion. acdream's
IAnimationHookSink documents these hook types as intended for "GfxObjMesh / renderer
state mutations" but no sink anywhere consumes them (only Particle/Lighting/Audio are
wired) -- confirmed via full-repo grep. Collision already works correctly via a
separate server-authoritative SetState wire message, independent of the animation
hook. This is feature-shaped rendering work (a per-part runtime alpha under the
mandatory N.5 bindless pipeline), not a quick fix -- filed for its own design pass.

Kept Issue188FadingDoorMotionTableInspectionTests.cs as a reusable MotionTable/hook
decoder for future "why doesn't this animate" questions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 20:10:45 +02:00
Erik
0efa7ed2a1 docs: close #137 (dungeon collision, user re-gate) + file #187 (door animation dispatch gap)
#137 collision scope is done — user confirmed all dungeon door types can be clicked
and passed through with no phantom blocks (adds to the 2026-07-06 corridor +
window/opening gates). The same check surfaced a SEPARATE visual bug: sliding doors
and "fading wall" gates don't play their open animation, only literal-name "Door"
entities do (GameWindow.cs:3128 IsDoorSpawn gates the reactive-motion-table rescue on
an exact display-name match). Filed as #187 for investigation before any fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 17:14:11 +02:00
Erik
4dea66b633 docs(#186): close — render InsideSide from dat PortalSide bit (fix 8257b9ba); retail trace overturned PICK + FLOOD-epsilon hypotheses
The live retail cdb trace decided it: retail roots at the connector 0118 at the
grey pose (NOT the PICK fork) AND still draws the player room 0116 from that root,
because retail's InitCell side test reads the dat PortalSide bit where acdream's
render path reconstructed the interior side from the cell AABB centroid (mis-sides
a thin connector). ISSUES #186 -> CLOSED; handoff gets a RESOLVED banner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 15:07:46 +02:00
Erik
9d35a9786f docs(#186): handoff — connector grey flap narrowed to the doorway-flap flood/pick family
Retail SEAMLESS at the same spot (user-confirmed) => real acdream bug: the eye seats
in a sparse 5-poly connector cell 0xF6820118 looking back at the player's room 0116,
and acdream drops 0116 (back-portal side-culled) so the doorway aperture shows the fog
clear color = grey; retail keeps 0116 drawn. RULED OUT: null-root/AD-20/AD-21 (root
valid, eyeInRoot=Y); the color-clear gating (retail's gated DrawCells Clear is
depth/stencil, post-LScape::draw). Next step = retail cdb trace (viewer_cell +
cell_draw_list at the grey pose) to pin viewer-cell PICK vs portal FLOOD, then a careful
frozen-render fix. Full handoff + apparatus + DO-NOT-RETRY + code/decomp sites in the doc.
Keeps the offline cell-geometry inspection test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:04:23 +02:00
Erik
cd42369581 docs(#186): file indoor->indoor grey flap at a connecting room (new house type)
Camera-direction-dependent grey (world background) at a top-floor connecting room.
Diagnosed by class (doorway-FLAP family): null viewer-cell root -> AD-21 outdoor
fallback; camera dependence -> AD-20 camera-eye viewer-cell resolution. Report-only
investigation next: ACDREAM_PROBE_FLAP capture at the spot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:26:54 +02:00
Erik
5a9df00aa9 docs(#185): close — REAL root cause = shadow part-id uint32 overflow (07c5b832)
Live gate passed. ISSUES #185 moved to DONE with the corrected root cause
(registration overflow, not the collision response). The handoff's convex-edge
theory and design-v1's grounding-retention theory are both recorded as superseded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:17:08 +02:00
Erik
99c22fad48 docs(#185): root cause = synthetic sliding-normal at a convex tread edge (#137/TS-4 family, artifact not geometry) 2026-07-08 07:28:35 +02:00
Erik
29e01c3829 docs: file #185 - local player jams half-way up outdoor stairs (house on stilts) 2026-07-08 07:14:06 +02:00
Erik
bd632f6f6d docs(#184): CLOSE — Slice 2 gate passed (players walk through, monsters collide)
Slice 2 (2a e1ac56cc extract + 2b ddb5a967 fork-collapse) shipped and the visual
gate PASSED (user: "Looks good"), closing #184. Mark ISSUES #184 DONE and the Slice 2
handoff DONE, both noting the review-driven correction: non-PK players WALK THROUGH
each other (retail PvP), so the player win is monster/terrain/wall collision, not
player-vs-player de-overlap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 06:40:29 +02:00
Erik
4b07b0f541 docs(#184): Slice 2 handoff — extract RemotePhysicsUpdater (2a) + unify the player/NPC fork (2b)
Deliberately deferring the last #184 piece (Slice 2) to a fresh focused session per
the user's call. This is a self-contained handoff: the #40 sweep-blip verdict (DEAD,
high confidence — Path B already runs that config stably + a Core test proves it),
the 2a-first-then-2b plan, the exact code sites (Path A :10194-10429, Path B, the
extraction seam :10152-10873, the shared-helper wrinkle), the 2b merge gotchas
(Path A's player-specific omega/compose/diagnostic bits; the coupled :5699 shadow
sync), the preserve-list, and the test/gate plan.

Corrects the earlier ISSUES characterization: Slice 2 is a GATED behavior change to
the frozen R4/R5 arc (players gain the sweep), not a no-gate refactor. The #184
symptom itself stays RESOLVED + gated (Slices 1+3).

Research: workflow wf_c6a2e2b9-833 (3-agent read-only sweep).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:31:01 +02:00
Erik
f0f6a92a4c docs(#184): mark the remote-creature de-overlap symptom RESOLVED + gated (Slices 1+3)
Both visual gates passed (crowd de-overlap + large-monster spacing). The reported
symptom is fixed end-to-end. Only Slice 2 (internal Path A unification + the
RemotePhysicsUpdater extraction) remains — an internal refactor with no visual
change, not urgent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:59:14 +02:00
Erik
37a94e1fa4 feat(#184): remote-creature de-overlap — placement-snap + shadow-follows-resolved (visual gate passed)
Packed monster remotes interpenetrate in acdream but barely in retail on the same
ACE. Retail de-overlaps them CLIENT-side: it sweeps every remote creature every
tick against neighbours' LIVE resolved positions (the collision shadow == the
resolved m_position, re-registered every moved transition step), with the server
position a gentle catch-up target (CPhysicsObj::MoveOrTeleport 0x00516330), not a
hard-snap. The collision math was already faithful; the bug was the reconciliation
(hard-snap) + the movement model (synth-velocity) + a stale shadow.

A first attempt (reverted) enqueued EVERYTHING and left the shadow at the raw
server position — it gate-failed with invisible monsters (an unplaced body blipped
over a huge distance into the sweep -> garbage pos) and the player stuck on offset
shadows. This redo fixes both root causes, mechanism-proven in a Core test first:

- NPC UpdatePosition routes through MoveOrTeleport with a PLACEMENT-SNAP: the body
  is snapped to the server pos when it is not already near it (first UP, no
  Sequencer to consume the queue, >96 m, or |Body - worldPos| > 4 m); only near DR
  corrections enqueue. This restores the body's placement authority (no invisible
  monsters). Airborne keeps the authoritative hard-snap.
- Grounded movement drives the body from the interp CATCH-UP (ComputeOffset ->
  InterpolationManager::adjust_offset, REPLACE dichotomy) instead of synth-velocity
  (get_state_velocity / SERVERVEL); MovementManager::UseTime runs unconditionally.
- SHADOW-FOLLOWS-RESOLVED: after each tick's sweep the collision shadow is
  re-registered at the resolved body (SyncRemoteShadowToBody), movement-gated
  (|Body - LastShadowSyncPos| > 1 cm). The per-UP :5669 raw-pos sync is now
  PLAYERS-ONLY, so an NPC's shadow is only ever written to its resolved body ->
  neighbours de-overlap against resolved bodies, the spread PERSISTS, and collision
  == render (no stuck-on-nothing). Landing clears the interp queue.

Preserved: airborne path, sticky #171 (gate + StickyManager overwrite of the seeded
frame), omega, the #173 bounce, landing, the node_fail_counter watchdog, and Path A
(player remotes, untouched -- Slice 2 unifies it).

Tests (RemoteDeOverlapMechanismTests): converging pair settles STABLE at 0.86 m
(barely overlapping = the retail look) WITH the shadow-sync vs <0.40 m (full
overlap) WITHOUT it; a third test drives the REAL InterpolationManager loop and
confirms the sweep absorbs the stall-blip (no pop-into-neighbour). 2-lens Opus
review (CONCERNS) addressed: movement-gated re-flood for the town-FPS risk;
players-only :5669; the blip-absorption test.

Register: retires TS-41 (SERVERVEL synth-velocity -> catch-up), narrows TS-44 (NPC
UP unified onto the interp queue; gate kept for orientation), adds AP-86
(shadow-follows-resolved impl) + AP-87 (MoveOrTeleport 4 m/no-Sequencer placement
snap). Known residual: the de-overlap sweep uses the human sphere for the mover, so
large creatures de-overlap at human radii (TS-46; Slice 3 plumbs Setup dims).

Visual gate PASSED (user: monsters visible + spacing much better). Core 2620 /
App 741 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:36:54 +02:00
Erik
e3c4c59b84 docs(#182): retire TS-3, narrow AD-25 + add AD-39/40/41, update ISSUES for the shipped rebuild
TS-3 (frames_stationary_fall accounting absent) retired — ported in the verbatim
UpdateObjectInternal rebuild. AD-25 narrowed to the remote-DR sweep (player half retired).
AD-39 (fsf ladder placement vs ACE's interleave), AD-40 (fsf bit-encode in the Core
writeback + CachedVelocity computed-not-consumed), AD-41 (candidateMoved gates only
handle_all_collisions) added. ISSUES #182 → rebuild shipped, awaiting the visual gate,
with the fsf-not-cached_velocity correction + the #137 Slice-3 residual note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 14:30:07 +02:00
Erik
80881ed6f1 docs(#182): crowd-collision investigation outcome + velocity-model rebuild design
The #182 CSphere port (96ae2740) failed its visual gate and introduced an
airborne "stuck in the falling animation" regression. A player-attributed retail
cdb trace (tools/cdb/retail-crowd-jump3.cdb) proved retail's LOCAL client fully
runs player-vs-creature collision (76 land_on_sphere, 188 COLLIDED, 130 SLID,
~78% OK, glides across) -- NOT server-authoritative (an earlier unfiltered
land_on_sphere=0 read was a false lead the attributed trace refuted).

acdream's same-repro capture: 50.9% OK, 22.4% stuck, 115 airborne-stuck. Root
divergence: retail CPhysicsObj::UpdateObjectInternal (0x005156b0, pc:283688) sets
cached_velocity = (resolved - old)/dt -- velocity from ACTUAL movement, so a
blocked jump collapses to ~0 -> gravity -> the player falls/glides. acdream
integrates velocity + reflects on collision (PlayerMovementController ~:1008-1069),
so the jump velocity (~18) persists against the creature -> hang.

Fix = verbatim rebuild of the per-frame player-physics loop (UpdateObjectInternal
chain), velocity model first, transition internals kept. Full design +
retail function inventory + the capture apparatus + retail target numbers:
docs/superpowers/specs/2026-07-07-player-physics-update-verbatim-rebuild-design.md.
Implementation deferred to a fresh session (user decision). Also files #183
(floating distant scenery, observed during testing). #182 stays as the base.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 13:07:56 +02:00
Erik
96ae274081 fix #182: port CSphere collision family — retail-faithful crowd wiggle (retires TS-45)
Humanoid creatures/players collide as body Spheres (ShadowShapeBuilder emits
Sphere-type shadows for a Setup with Spheres + no CylSpheres), so player-vs-monster
crowd contact ran through Transition.SphereCollision — a hand-rolled 3-D wall-slide
(register TS-45), NOT a port of retail CSphere::intersects_sphere. It shaved no eps,
force-pushed each contact RADIALLY to a fixed combinedR+1cm shell, ignored the head
sphere, and always returned Slid. In a crowd the opposing radial de-penetration
pushes from neighbours fight each other -> the player wedges and can't wiggle free
(the user's live report).

Port the full CSphere family verbatim — dispatcher 0x00537A80 + step_sphere_up /
slide_sphere / land_on_sphere / collide_with_point / step_sphere_down — the direct
analog of the 2026-07-05 CCylSphere port (#172). The grounded slide now routes
through the shared crease SlideSphere (0x00537440, #116-Ghidra-confirmed) ->
tangential shuffle along the contact toward gaps, retail-faithful. isCreature
(target creature/missile) gates OFF the stand-on/land-on branches (2 & 5). ACE
Sphere.cs = readable oracle; pseudocode doc 2026-07-07-csphere-collision-family.

Retail-faithfulness verified: CTransition::validate_transition (0x0050aa70:272593)
reverts curr_pos on any non-clean-OK step, so a deep-mutual-overlap start wedges in
retail too — the realistic crowd-edge graze slides free (SphereCollisionFamilyTests
slide-around trajectory: player grazes a creature's SW, curves around its west side,
continues N).

TS-45 retired, AP-84 added (PerfectClip TOI dead in M1.5). Core 2603/0, App 741/0.
Pending user visual gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 11:25:56 +02:00
Erik
3120f8ae08 docs(ISSUES): correct #138 status to resolved (residuals #146/#147)
Position-desync fixed (#145), server-object re-hydrate shipped (AP-48), avatar-vanish fixed + user-confirmed (afd5f2a). The stale pending-gate status misrepresented the M1.5 critical path; live follow-ups are #146/#147.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 08:45:58 +02:00
Erik
554af5042d park #177: retail cdb trace DISPROVES the portal-flood theory
Attached cdb to live retail (PDB MATCH), broke on PView::DrawCells (0x005a4840),
dumped cell_draw_num + cell_draw_list cell ids + the eye
(Render::FrameCurrent->viewer.viewpoint) while descending the Facility Hub spiral.
Retail's flood is dynamic and IDENTICAL in character to ours: from the spiral cells
it swings num 3->27 with gaze and collapses to 3 cells at many poses (cam=015d ->
{015d 015e 015f}). Our flood does the same (3->43). So retail does NOT keep the
staircase where we drop it -- the flood is exonerated as the cause.

Session trail (all in ISSUES #177): ruled out lighting, membership, camera coherence,
the collision sweep, the 0178/0182/0183 handoff cells, and edge-on eye-in-opening
(fix#1 shipped -> visual-gate-failed -> reverted, PortalVisibilityBuilder + AP-86 both
restored exactly). Freshest un-chased lead: the steps are STATIC objects (GfxObj
0x010000DE x6/cell) drawn via the viewcone cull, not cell shell.

Adds: Issue177StairDescentCameraFloodTests (real-camera+flood + composition +
flood-depth characterization pins) and a reusable retail-cdb capture toolchain
(tools/cdb/pview-verify.cdb, pview-spiral2.cdb with the correct top-level-qd detach --
qd in a CONDITIONAL bp action does NOT fire and strands cdb attached).

No production code change (fix#1 reverted). PARKED per user; M1.5 critical path next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 08:43:21 +02:00
Erik
c84dc49a63 docs: park #181 with the full elimination ledger; #177 stairs pickup handoff + companion prompt
#181 parked per user decision (the flicker survived the 7-fix ladder; every fix individually verified and standing). ISSUES #181 carries the shipped-commit list, the evidence-backed eliminations, and the four still-open leads (top: capture a clean frame pair at THE USER'S pose - never done). New handoff docs/research/2026-07-06-177-stairs-pickup-handoff.md: the stair-cell admission miss (0x0178/0x0182/0x0183), the existing Issue176177 replay scenarios as the entry point, the 0x0181 sliver adjacency lead, the lighting DO-NOT-REOPEN, and the companion prompt. Render digest re-bannered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:53:23 +02:00
Erik
cace430299 diag #176/#181: artifact CHARACTERIZED - static ~10x-hot magenta wash zebra-striping wall geometry; root = weenie lamps on the dynamic light curve (A7 fix #2); retail static curve Ghidra-verified (calc_point_light 0x0059c8b0)
VSync-on test (30fps): stripes remain => not tearing (windowed + DWM never tears - the tear framing was structurally wrong). Clean captures at the user's spot: the scene is pixel-static except idle anim; the 'stripes/triangles' are the corridor wall's angled braces silhouetted against blown-out saturated magenta. Server-weenie stationary lamps register isDynamic:true (1/d, range x1.5); retail statics use f=(1-d/range)*intensity*wrap/d^3 beyond 1m, range=falloff*1.3, per-channel colour clamp - ~10x dimmer at 3m. Next: A7 fix #2 (static curve; isDynamic decided by motion, not origin) + conformance pin, then the combined #176/#180/#181 gate. a7 pseudocode doc SS1.6 updated with the verified curve; render digest re-bannered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:43:43 +02:00
Erik
378316af15 docs #181: the vsync decision gate - all camera/sweep mechanisms now Ghidra-verified faithful; residual mm limit cycle likely retail-class, visible only via unsynced tearing
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:31:36 +02:00
Erik
d4ff80cb2b diag #181: retraction + exoneration - the 'scissor rect' was the user's snip-tool marquee; applied light sets FROZEN across all vis=32 frames
The seam-diff run ([seam-cell] applied sets correlated with [flap] vis across 107k parked frames): zero applied-set changes after startup hydration, including all 1,394 vis=32 frames - the vis 31<->32 flap has no lighting consequence. The t0b/still-* pixel-diff evidence was contaminated (the user was dragging the snipping-tool marquee + the chat window overlapped the game). Camera, pool, applied sets, and the sliver cell's own geometry are now ALL probe-exonerated. What stands: the flicker per the user's eyes and their clean screenshots (pink wash patches + a brick-textured stripe across the floor). Next: unobstructed-window captures while the user confirms the flicker is live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:50:08 +02:00
Erik
d366f36557 diag #181 (refined): 0x0181's region is a zero-area sliver; the amplifier is per-drawn-cell state shifting with the cell list, not light-pool scoping
Diagnostic_FlappingCellViewRegion_SliverOrLarge: at the flap pose the admitted region is one degenerate triangle (ndcArea ~0), and ClipPlaneSet.From handles degenerates correctly (area<1e-7 -> Empty) - the cell's own gated geometry costs ~zero pixels either way. The a7 pseudocode CORRECTION-2 re-read kills the flood-scoped-lights framing (the pool is already resident+player-anchored since d8984e87). Remaining suspects: per-cell light-set SSBO slot assignment (SelectForCell) or the seal/punch assembly keyed to the drawn-cell list. Next instrument: parked ACDREAM_PROBE_SEAMDRAW=1 run, diff the washed cell's [seam-blk] applied-set lines between vis=31 and vis=32 frames. ISSUES #181 + render digest updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:46:49 +02:00
Erik
864e4d9e75 diag #181: root-cause chain complete - flapping cell 0x0181, wall-press excitation, and the REAL defect: camera-flood-scoped light application
Issue181VisFlapReplayTests: headless flood replay at the live parked pose names cell 0x8A020181 - +-0.5mm eye perturbations flip its admission (at yaw15/pitch-20 for every direction). Issue181CameraParkStabilityTests: the camera loop parks BIT-EXACT with static inputs (0.00um over 2000 frames) - the live ~1mm/frame wander is the wall-press equilibrium (sought steps a*gap ~4mm into the wall per frame, the clip lands within adjust_to_plane's parametric 0.02 window; player physics bit-frozen per [resolve]). That wobble is retail-class. The defect is OURS: the A7 adaptation scopes light application to the camera flood's per-frame admission, so a sliver cell flapping flips whole lit regions; retail computes light reach once at registration (Render::add_static_light -> CObjCell::add_lights), camera-independent. Fix direction: registration-time per-light reach sets via the portal graph (pseudocode docs/research/2026-07-06-a7-per-cell-lighting-pseudocode.md). ISSUES #181 updated with the full chain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:04:34 +02:00
Erik
d9a4ca2355 docs #180/#181: bank the session state - both camera fixes verified, the flicker split to #181 (portal-flood vis flap on micron eye noise)
ISSUES: #180 marked both-fixes-shipped+log-verified; #181 filed with the full live evidence (vis 31<->32 every ~100-200 frames across a 517k-frame parked session, the dashed scissor-rect captures, the isolation-confound note - every pre-#180 isolation ran while the camera strobe was live); #176 status updated (site-A static stacking hole closed 87cddce2, residual = #181). Digests updated in claude-memory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:54:01 +02:00
Erik
48aaab811c fix #180: port retail's stateful camera sought-position - the sweep target converges onto the wall contact
The camera-collision sweep strobed the eye 0.27m every ~5-10 frames while
the compressed chase boom moved along corridor walls (pulledIn 0.27<->0.53
on ~1.4mm input drift): RetailChaseCamera re-demanded the FULL-length ideal
boom from scratch each frame, so the pivot->eye ray re-rolled the same
knife-edge r+-eps graze on the double-faced slabs every frame, and its two
first-contact solutions tear-interleaved at ~1700fps into the #176
"stripes/triangles".

Retail never re-rolls that ray. CameraManager::UpdateCamera (0x00456660)
interpolates FROM THE CURRENT SWEPT VIEWER toward the desired pose
(interpolate_origin/rotation, stiffness 0.45 x dt x 10, clamped) and the
result becomes viewer_sought_position (SmartBox::PlayerPhysicsUpdatedCallback
0x00452d60); update_viewer (0x00453ce0) sweeps pivot->SOUGHT. Pressed against
a wall the sweep ray extends one interpolation step past the contact
(sub-mm at high fps), so a bistable graze can move the eye by at most that
step - the strobe is structurally impossible. A 0.4mm/2e-4 dead-band parks
the sought exactly on the viewer when converged (0x00456fcd-0x00457035).

- RetailChaseCamera: _dampedEye -> _soughtEye + _publishedEye (retail's two
  Positions); lerp base = the published (swept) viewer; sweep targets the
  sought; total-fallback (ViewerCellId==0) resets the sought like
  set_viewer(player_pos, 1). The old "collision must NOT feed back into the
  damped state" comment had the coupling backwards - what stays clean is the
  transient desired pose, not the sought.
- SweepEye untouched (faithful update_viewer port, exonerated by the #180
  investigation).
- Tests: the old pin asserting instant full re-extension after a clamp
  (the divergence itself) replaced with four retail pins: gradual
  re-extension, sweep-target-converges-onto-contact, total-fallback
  re-extends from the player, wall-press glide stability.
- Pseudocode doc: docs/research/2026-07-06-camera-sought-position-pseudocode.md
  (UpdateCamera tail incl. the sought derivation + set_viewer reset semantics
  + Frame interpolate/close_rotation).
- Register: AD-37 (forward-vector nlerp vs quaternion slerp), AD-38
  (init-at-full-extension vs retail re-extend-from-player) - both
  pre-existing, identified during the decomp reading.

Suites green (Core 2599+2skip / App 729+2skip / UI 425 / Net 385).
Pending: autonomous visual verify + user gate (#180 + the #176 re-gate).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:49:01 +02:00
Erik
6aabe0b5ac diag #176/#180: isolation toggles pin the residual stripes on the camera sweep, not the render
The #176 gate-2 failure ("stripes/triangles flickering when the camera
is pushed into walls; nothing when zoomed out") is NOT a render defect.
Isolation apparatus added this commit:

- ACDREAM_LIGHT_DEBUG shader modes (mesh_modern.vert/frag + uLightDebug
  upload in EnvCellRenderer/WbDrawDispatcher): 1 = ambient-only,
  2 = dynamics killed, 3 = raw vLit field (texture ignored). The
  pattern SURVIVES mode 3 -> not texture; lives above the light data.
- ACDREAM_CLIP_DEBUG=1 (RenderingDiagnostics.ClipDebugNoShellTrim +
  the EnvCellRenderer slot-fill gate): shell pass draws cells WHOLE
  (retail's shape). Pattern survives -> the per-cell clip trim is
  exonerated.

With every render suspect dead, an autonomous visual loop (synthetic
back-into-wall input + GDI window captures + the [flap-sweep] probe)
pinned the mechanism numerically: at a compressed moving boom the
camera-collision sweep is BISTABLE - consecutive sweeps with ~1.4 mm
input drift flip the first-contact solution 0.27 m along the boom
(pulledIn 0.27<->0.53, every ~5-10 frames, all 368k sweeps ok=True),
and at ~1700 fps unsynced every monitor refresh tear-interleaves the
two views = the stripe/hatch patterns. Filed as #180 with the retail
anchor: viewer_sought_position is STATEFUL (SmartBox 0x00452d75 feeds
the CURRENT swept viewer into CameraManager::UpdateCamera 0x00456660
and assigns the return to the sought, 0x00452d84) - the target
converges to the collided position instead of re-rolling the full
knife-edge ray per frame like our RetailChaseCamera does. SweepEye
itself ports update_viewer 0x00453ce0 faithfully and is exonerated.

Also recorded in #176: the site-A weenie light-registration leak (a
portal's I100 light stacked x2->x4 over one session as re-CreateObject
re-registered it under fresh entity ids).

The #176 lighting fix (d8984e87) remains live-verified; #176 re-gates
after #180 lands. ISSUES: #180 filed, #176 updated. Suites: Core
2599+2skip; toggles inert by default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:19:38 +02:00
Erik
d8984e877f 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>
2026-07-06 15:12:31 +02:00
Erik
8cb3176daa feat(lighting): SelectForCell (all dynamic lights per EnvCell) + #176 handoff — residual is a runtime z-fight
cdb trace of LIVE retail (tools/cdb/issue176-floor-light.cdb, binary<->PDB MATCH) PROVED retail
applies ALL its dynamic lights — 4 intensity-100 magenta portal lights (d3dIdx 3-6, falloff 6) +
the viewer fill (d3dIdx 1, 2.25) — as D3D hardware lights to EVERY Facility Hub cell, every frame,
stable. So the faceted purple wedges on the floor are retail-FAITHFUL. acdream did a per-cell
SelectForObject sphere-overlap 8-cap for cells, so the portal set could differ/flip per cell.

- LightManager.SelectForCell (retail minimize_envcell_lighting 0x0054c170): ALL dynamic lights
  applied unconditionally (shader range cutoff zeroes non-reaching = D3D hardware range), then
  nearest static torches fill remaining slots. Wired into EnvCellRenderer.GetCellLightSet.
  Objects keep SelectForObject (minimize_object_lighting). Pins:
  SelectForCell_AppliesAllDynamicLights_EvenOutOfReach + _SameDynamicSet_ForCellsFarApart_NoFlap.
- Apparatus: [light-detail] gains owner/cell/dyn (pinned the culprit = 2 portal weenies
  0x000F4247/48 in 0x8A020118/19, intensity=100 magenta); CellVertexNormals_SmoothOrFaceted_Dump
  (corridor floor uses SMOOTH per-vertex dat normals, not flat); tools/cdb/issue176-floor-light.cdb.

#176 RESIDUAL is NOT this fix. It's a RUNTIME draw z-fight in the seam floor. Eliminated (evidence):
NOT lighting (per-light cap + this both no-change), NOT membership (render cell 0x8A020164 stable
100% of 188k frames / 526 angles, res=None), NOT dat geometry (coplanar sweep empty at z=-6 floor
incl. cell 0164). NEXT = RenderDoc pixel-history. Full handoff + DO-NOT-RETRY:
docs/research/2026-07-06-176-seam-floor-zfight-handoff.md. Suites green: Core 2599 + 2 skip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 13:55:16 +02:00
Erik
c500912bf8 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>
2026-07-06 00:35:01 +02:00
Erik
d591e3bbe5 revert #176/#177 cap raise: the uncapped light pool exposes unported per-cell reach semantics — defer to A7
The MaxGlobalLights 128->1024 fix (4d25e04d) was live-tested and made
the eviction pops stop — but with the full 366-fixture pool active,
three unported retail lighting semantics dominate the Facility Hub:

(a) lights reach THROUGH solid floors/walls: retail registers lights
    per-CELL (insert_light 0x0054d1b0) so the under-room portals'
    purple light never touches the corridor above; our flat
    sphere-overlap selection has no reach/occlusion notion — rooms
    washed magenta (user screenshot).
(b) stationary weenie fixtures ride the DYNAMIC 1/d falloff (~9x
    retail's static 1/d3 bake curve at 3m) — the #143 isDynamic
    assignment is wrong for ACE-served world fixtures.
(c) an unexplained striped z-fight-like artifact on lit floor regions
    (user screenshot; no coincident dat geometry — the coplanar-pair
    sweep came back empty; not a striped texture — all corridor
    surfaces are plain Base1Image stone).

Reverted to 128. The cap is now documented as a LOAD-BEARING STOPGAP:
it accidentally approximates per-cell reach by keeping the pool local
to the camera. The #176/#177 root cause (cap eviction popping per-cell
light sets) stays CONFIRMED and fully documented; the real fix is the
A7 dungeon-lighting arc: per-cell light registration + the static
fixture curve + the stripe hunt, THEN uncap. The desired-end-state pin
is kept as Skip with the full pointer. Register row AP-85 rewritten to
match reality; ISSUES #176/#177 back to OPEN with the complete
mechanism story.

Suites: Core 2591+3skip / App 719 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 23:11:18 +02:00
Erik
4d25e04d83 fix #176 #177: the camera-capped light snapshot evicted visible cells' torches — per-cell lighting popped at seams
The probe launch discriminated it: the user reproduced the purple floor
flash while [light] (ambient branch) and [pv-input] (portal flood) read
provably healthy — eliminating the last CPU-side theories and exposing
the one channel the probes could not see: per-cell 8-light set
composition.

BuildPointLightSnapshot kept the MaxGlobalLights=128 point lights
nearest THE CAMERA; the Facility Hub registers 366 fixtures, so 238
were evicted per frame by camera distance. SelectForObject (faithfully
camera-independent, and unit-pinned as such) could only choose from the
surviving 128 — an in-range torch of a visible cell that ranked past
the cap dropped out of that cell's 8-set, so per-cell Gouraud lighting
flipped as the chase boom swung the camera:

- #176: the flipping unit is a CELL -> discontinuity lines at exactly
  cell-seam granularity; a torch-losing floor drops to dim blue-grey
  stone at 0.2 ambient (the perceived purple), camera-angle dependent.
- #177: a stair room whose torches all ranked past the cap rendered at
  bare 0.2 ambient (near-black = 'not visible'); approach re-admitted
  them ('pops into existence'); the sweeping boundary dropped the
  ramp's lights mid-descent ('disappears on the last step'). The
  geometry never vanished - its lights did.

Retail's minimize_object_lighting (0x0054d480) has NO global
camera-nearest pool cap (lights register per cell, insert_light
0x0054d1b0). Fix: MaxGlobalLights 128 -> 1024, a non-biting safety
valve (GlobalLightPacker grows to fit; 64 B/light). Register row AP-85.

TDD pin: PointSnapshot_HubScaleLightCount_ObjectSelectionIsCameraInvariant
(RED at 128 with a Hub-scale 401-light layout, GREEN at 1024). The
pre-existing camera-independence pin covered the SELECTOR but not the
SNAPSHOT it selects from - the pop re-entered one stage upstream.

Suites: Core 2588 / App 719 / UI 425 / Net 385 green. Pending user gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 21:55:43 +02:00
Erik
b8e9e204ad docs+test #176/#177: 12 mechanisms refuted, apparatus shipped, probe protocol staged
The dungeon render pair (purple seam flash + stair pop-in) resisted
desk root-causing, but the investigation narrowed the space to two
live theories and shipped permanent apparatus:

- Issue176177DungeonSeamInspectionTests: dat truth — corridor floors
  ARE textured drawn PortalSide portal polys; the reciprocal is NoPos;
  the 'stairs' are 0x8A020182's ramp shell (vertical portals, zero
  statics); CellBSP partitions exactly at portal planes; DXT1 textures
  carry zero transparent-mode texels.
- Issue176177FacilityHubFloodReplayTests: production-matched flood
  replays — approach/descent/gaze-sweep/walk all healthy with coherent
  inputs; ScenarioE pins the flood's collapse-to-root sensitivity under
  incoherent (root, eye) pairs.
- Issue176177SeamTransitLagTests: the resolver flips cells within one
  tick-step of the portal plane — the 0.33-0.47m [cell-transit] 'lag'
  in the gate logs is speed*tick quantization, not membership error.

Refuted (do NOT retry — ledger in the research doc): placeholder
texture, reciprocal z-fight, seal z-fight (seals only fire for
OtherCellId==0xFFFF), root/eye incoherence (production camera sweep is
mm-exact at planes), flood bistability, #119-class statics, undefined
DXT mips (both paths decode DXT->RGBA8; the compressed-array branch is
dead code), DXT1 alpha, fog mix (ramp ~538m), lightning leak (flash==0
in production), viewer-light pops (smooth (1-d/range) ramp).

Filed #178 (A8 double-sided shell stopgap still live) and #179
(lightning flash lacks an indoor gate — dormant). The purple can only
be the fog clear color (undrawn pixels) or the outdoor ambient+sun
tint; discrimination needs ONE probe launch (ACDREAM_PROBE_LIGHT +
ACDREAM_PROBE_PVINPUT + ACDREAM_PROBE_CELL) — protocol in
docs/research/2026-07-06-176-177-render-pair-investigation.md.

Suites: Core 2587 / App 719 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 21:20:03 +02:00
Erik
bbfacc49df docs #137: window-climb gate PASSED (user-verified, regression sweep clean)
The corrected 1.835m collision capsule blocks the window alcove like
retail; doorways, seams, and stairs unaffected by the taller head.
Remaining #137 scope: the doors half (block/pass per open state, the
#175 pose fix's pending gate).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:01:30 +02:00
Erik
aa96d7ad77 fix #137 (window climb): the player's collision capsule topped out at 1.2m — head sphere 0.63m too low
The 'run into the last corridor window and pop up through its roof'
report: the live callers passed sphereHeight: 1.2f into
SpherePath.InitPath, whose head-sphere formula (height - radius) put the
head sphere center at 0.72 - the capsule top at 1.2m. The top 0.63m of a
1.83m character had NO collision, so at the corridor-end window alcove
(0x8A020179 -> 0x8A02017E: 0.70m sill face, 1.3m opening, sloped funnel
behind) the step-up's placement never saw the head overlapping the
lintel solids and let the player climb in head-through-roof.

Dat truth (HumanSetup_CollisionSpheres_DatTruth): Setup 0x02000001
spheres = (0,0,0.475) r=0.48 and (0,0,1.350) r=0.48 - capsule top 1.83 =
Setup.Height 1.835. Retail collides with that sphere list verbatim
(CPhysicsObj::transition 0x00512dc0 -> init_sphere(GetNumSphere,
GetSphere, m_scale)).

Fix: PlayerMovementController + the GameWindow remote resolve now pass
sphereHeight: 1.835f (capsule top; head center 1.355 vs dat 1.350).
InitPath unchanged - captured-input replay fixtures (recorded 1.2
inputs) stay byte-identical. Register TS-46: the (radius, capsule-top)
scalar approximation of the Setup sphere list (5mm foot/head offsets;
remotes use human dims) with the retire path (plumb the sphere list).

Pins: WindowOpening_HeadCannotFit_EntryBlocked (22-frame walked approach
wall-slides at the sill, never enters 0x8A02017E) +
WindowAlcove_RaisedPlacement_HeadInLintelSolid_Collides (Path-1
placement rejects the raised head in the lintel solids) + the
WindowShaft_FullPolyDump / HumanSetup dat inspections.

Suites: Core 2562 / App 713 / UI 425 / Net 385, 0 failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 19:57:11 +02:00
Erik
1494f10cb5 docs #137: corridor gate PASSED; file #176 (purple seam flash) + #177 (stairs pop-in) + the window-climb repro
User gate 2026-07-06 evening: 'not collision anymore. Good.' — the
corridor-phantom arc (stub sliding-normal leak, slide_sphere opposing
branch, CheckOtherCells stale footCenter) is user-verified fixed.

Same gate surfaced three follow-ups:
- #137 remaining scope: the window/opening climb — running into the last
  corridor window steps the player up into the opening, head+camera
  through its roof. Log anchor: transit 0x8A020179 -> 0x8A02017E at
  (90.209,-41.774,-5.209) — sill ~0.8m > stepUpHeight 0.6, climbed anyway.
- #176 (NEW, render): purple floor flashing at cell seams, camera-angle
  dependent — survives the physics fix, so render-side; the seam floors
  ARE portal polygons to the under-rooms (placeholder-surface suspicion).
- #177 (NEW, render): stairs between levels pop in/out — invisible from
  the corridor, appear on entering the room, vanish on the last step
  down. The #119 visibility class, dungeon edition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 19:32:48 +02:00
Erik
d4869154d2 fix #137 (seam shake): CheckOtherCells queried remaining cells at a stale pre-climb center
The P2 cellar-lip lesson one loop deeper. CheckOtherCells takes footCenter
by value and used it for every cell in the loop — but a mid-loop query can
MOVE the sphere: at the Facility Hub cell boundaries the neighbor's ramp
floor full-hits, step_sphere_up climbs the foot +0.6mm and returns OK, and
the loop continued querying the REMAINING cells (including the under-room,
portal-ring-3) at the pre-climb height — 0.4mm inside the double-faced
floor slab, grazing its underside (the under-room's ceiling) within the
near-miss window. That dispatched a neg-poly step-up with a DOWNWARD
normal, whose failure funneled into slide_sphere's opposing branch ->
synthetic reversed-movement collision -> Collided -> revert, every frame:
the seam shake (and, pre-mechanism-2-fix, the original absorbing wedge's
entry).

Retail check_other_cells reads the LIVE sphere_path.global_sphere for
every cell (each cell's find_collisions receives the transition itself,
pc:272717+). Fix: re-read footCenter = sp.GlobalSphere[0].Origin at the
top of each loop iteration.

All three Issue137CorridorSeamReplayTests repros un-skipped: the
snapshot-exact west-boundary crossing (capture tick 4101), the east
deep-straddle, and the clean-run lifecycle all GREEN. Full suites: Core
2556 / App 713 / UI 425 / Net 385, 0 failures.

Visual gate pending: corridor run + the purple seam flashing re-check
(expected to be the render exposing the same per-frame oscillation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 19:15:05 +02:00
Erik
dbddad7a5e test #137: the seam shake reproduced offline — third mechanism characterized
The corridor gate FAILED with a changed symptom: shaking at cell seams
(+ purple floor flashing there) instead of the dead stop. Deep-probe
session (step-walk/push-back/indoor-bsp + full resolve capture) traced
the complete chain; docs/ISSUES.md #137 carries it. Short form:

- Corridor floors are double-faced portal slabs over under-rooms; the
  resting foot sphere lives within half a millimeter of three hit/straddle
  thresholds there.
- Crossing a boundary, the foot penetrates the neighbor ramp slab by
  ~0.4mm, steps up onto it successfully (+0.6mm lift, stepped=True) —
  and the lifted check position is then LOST: the following pass runs at
  the unlifted height (the P2 stale-snapshot class; retail step_up
  0x0050b6cc restores only on FAILURE).
- The unlifted re-test grazes the under-room's ceiling (the slab
  underside) within the near-miss window, dispatches a neg-poly step-up
  with a DOWNWARD normal, whose nested step-down finds no walkable at
  exact tangency -> StepUpSlide -> slide_sphere opposing branch ->
  reversed-movement collision -> Collided -> revert. Every frame = shake.

Apparatus committed:
- Issue137CorridorSeamReplayTests: 3 deterministic offline repros
  (snapshot-exact west-boundary from the capture, east deep-straddle,
  the clean-run pin), currently Skip='#137 seam shake' pending the fix.
  Key: THREE portal-ring hydration (the under-room 0x8A020166 is ring-3;
  with fewer rings the flood can't add it and the bug vanishes) + live
  Setup step heights (0.6/1.5) + probe-buffer capture for line-diffing
  offline vs live traces.
- Issue137CorridorSeamInspectionTests: portal-poly world spans (exposed
  the visual-vs-physics polygon-id conflation and the floor-portal
  topology), physics-BSP leaf membership walk, hit-normal candidate
  sweep (|align| both windings), downward-poly sweep.

NEXT: read TransitionalInsert's attempt loop against retail 0x0050b6f0,
find the restore that clobbers the successful step-up position, fix,
un-skip. The purple seam flashing is expected to be the render exposing
the same per-frame oscillation - re-check after the physics fix.

Suites: Core 2553 / App 713 / UI 425 / Net 385, 0 failures (5 skips =
2 pre-existing + the 3 parked repros).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 19:05:19 +02:00
Erik
e8651b3819 fix #137 (corridor phantom resolved): slide_sphere opposing branch returns Collided; the 'wall' was synthetic
The mechanism-1 theory (PortalSide portal polys solid in our physics set)
is REFUTED for the corridor repro, and the remaining half of the phantom
is fixed — no cdb session needed:

- The live hit normal (-1.00,0.03,-0.03) matches NO dat polygon: a
  world-space sweep of both seam cells + every portal-adjacent neighbor
  (CorridorSeam_FindPolygonMatchingLiveHit) returns zero candidates. The
  normal is the negated movement direction — the SYNTHETIC value
  slide_sphere's opposing-normals branch records (reversed = -gDelta).
- Cell 0x8A02016E has IDENTITY rotation (the prior session's 'rotation
  maps the portal planes into the -X wall' was a misattribution). The
  PortalSide polys to 0x011E are +-Y planes 1.4 m beside the player's
  track, perpendicular to the +X run — pos_hits_sphere's directional
  cull rejects them for that movement. They ARE referenced by the dat's
  physics-BSP leaves (CorridorCell_PhysicsBspLeafMembership), so retail
  tests them too when approached into their plane; the dat's
  keep-PortalSide / strip-ExactMatch asymmetry reads as intentional
  (solid window/grate-class portals). No portal-poly filter — exactly
  the blanket-skip the pickup warned against.
- Port fix: CSphere::slide_sphere's opposing-normals branch
  (0x005375d7-0x0053762c) records the reversed displacement and returns
  COLLIDED_TS; our port returned OK ('retail returns OK here' was a
  decomp misread), letting the step complete as-is with the synthetic
  collision normal that validate's epilogue then persisted as the
  sliding normal the wedge absorbed on. TransitionTypes opposing branch
  now returns Collided; pinned by
  SlideSphere_OpposingNormals_ReturnsCollided_WithReversedDisplacementNormal
  (RED->GREEN).
- Dat-backed replay (Issue137CorridorSeamReplayTests) reproduces the
  live hit frame verbatim (same in/out to the millimeter, same 016E->017A
  transit, same +8mm settle) and runs the corridor CLEAN: hit=no, no
  sliding normal persisted, six further forward frames advance freely.
- Inspection tests extended: physics-BSP leaf membership walk +
  hit-normal candidate sweep + downward-poly sweep (all report-style,
  dat-gated). Pickup prompt banner'd SUPERSEDED; ISSUES #137 updated
  (door half stays open); audit doc extended with the resolution.

Suites: Core 2551 / App 713 / UI 425 / Net 385, 0 failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:27:40 +02:00
Erik
a11df5b8d3 fix #137 (mechanism 2): BSP full-hit stubs leaked sliding normals — the corridor absorbing wedge
The Facility Hub corridor dead-stop's second half: after one seam hit,
every forward resolve returned ok=False hit=no with zero advance. The
body-persisted SlidingNormal (-1,0,0) projected the exactly-anti-parallel
corridor push to zero in AdjustOffset and the step loop aborted at step 0
before any collision test could refresh the state.

Audit (docs/research/2026-07-06-137-sliding-normal-lifecycle-audit.md):
retail's only in-transition sliding-normal writer is validate_transition
(0x0050ac21); the whole sphere/BSP layer writes NONE (grep-verified), and
the body persistence (SetPositionInternal 0x005154c2, SLIDING_TS bit sync
0x005154e1) runs only on transition success. Our BSPQuery Contact-branch
full-hit responses were stubs (SetCollisionNormal + SetSlidingNormal +
return Slid) where retail dispatches the real slide_sphere — so the seam
hit (a SUCCESSFUL full-advance resolve per the live log) persisted the
phantom wall's normal, which retail's lifecycle structurally cannot do.

- BSPQuery Contact foot full-hit fallback + head full-hit now route
  through Transition.SlideSphereInternal (CSphere::slide_sphere
  0x00537440 — in-frame slide, no sliding-normal write; ACE
  BSPTree.cs:202,310-316). The dead stub is rewritten as the faithful
  BSPTREE::slide_sphere wrapper.
- PhysicsEngine sliding writeback gated on ok (retail success-only
  placement; behaviorally latent, removes the failed-frame leak class).
- Register: TS-4 amended (Path-6 steep-tangent sites still write the
  normal — now documented), TS-45 added (SphereCollision's write — same
  leak class, left for a follow-up out of #171's blast radius).
- Pins: Issue137SlidingNormalLifecycleTests — both site pins RED->GREEN,
  plus the retail persist/absorb/clear wall lifecycle (validate-write
  persistence, faithful absorbed anti-parallel frame, oblique escape
  clears the bit). BSPQueryTests full-hit pin updated to the real slide.

Mechanism 1 (PortalSide portal polys solid in the physics set) stays
OPEN - #137 not closed; the corridor re-test rides that session.

Suites: Core 2545 / App 713 / UI 425 / Net 385, 0 failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:08:29 +02:00
Erik
59705cf647 docs #137: record the portal-poly oracle greps (question stays open)
CCellStruct::UnPack loads physics polys + BSP verbatim (no portal
stripping); the CPolygon test chain is pure geometry; CCellPortal
carries portal_side/exact_match but nothing in the BSP tests consults
it. Retail's PortalSide passability is therefore not a load filter or
poly flag — remaining candidates are the transit/membership test
order or a stippling/sidedness interaction. Next: cdb-attach retail
at the 0x8A02016E corridor per the step -1 protocol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 17:22:05 +02:00
Erik
17ce23ce38 docs #137: corridor phantom characterized — PortalSide polys solid + sliding-normal wedge
Facility Hub corridor repro (probe + dat evidence, both mechanisms
pinned):

1. PortalSide portal polygons live IN CellStruct.PhysicsPolygons and
   acdream collides with them as plain solid geometry. Cell
   0x8A02016E's portals to 0x011E (polys 1/3/5, flags=PortalSide) are
   present in the physics set while every ExactMatch portal in the
   same cell is absent — the cell's rotation maps those portal planes
   to the world -X wall the player hit mid-corridor
   (launch-175-verify2.log:42858, n=(-1,0,0)). Retail must honor the
   portal side; oracle grep required before fixing.

2. After the single seam hit, the body-persisted SlidingNormal
   projects every subsequent forward offset to exactly zero in
   AdjustOffset - the step aborts BEFORE any collision test can
   update state (ok=False hit=no, zero advance), an absorbing wedge
   escaped only by strafing ("push through on the side"). The #116
   slide-response family: retail re-derives slide state per frame
   (get_object_info pc:279992).

Inspection fixture: Issue137CorridorSeamInspectionTests (dumps
physics-vs-portal polygon membership for the two corridor cells).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 17:20:05 +02:00
Erik
355e389d4c fix #175: door BSP collision poses at the motion-table closed pose
The Facility Hub double door (Setup 0x02000C9D) embeds the player into
the visual panel from one side and blocks with a phantom slab on the
other: its Setup PLACEMENT frames pose the two panels AJAR (yaw
-150/-30 deg, 0.44 m behind the doorway plane — dat-confirmed by the
Issue175 inspection) while the rendered door poses them CLOSED from
the wire-supplied motion table via the sequencer. ShadowShapeBuilder
read placement frames, so the 1.66x0.29x2.95 m physics slabs
registered at the ajar pose. Retail tests each part's LIVE
CPhysicsPart pose — for an idle door, the motion table's default
(closed) state.

Fix: ShadowShapeBuilder.FromSetup gains partPoseOverride (BSP part
shapes only); RegisterServerEntityCollision derives it from the spawn
MotionTableId via GameWindow.MotionTableDefaultPose (default style ->
first cycle -> LowFrame part frames). Null/short poses fall back
per-part to placement frames — table-less entities and landblock
statics unchanged. One-shot snapshot vs retail's per-frame live pose
is register row AP-84 (equivalent for the door lifecycle: closed ==
default pose; open == ETHEREAL bypasses collision, #150).

Pins: FromSetup_PartPoseOverride_ReplacesPlacementFrames /
_NoOverride_KeepsPlacementFrames / _ShortOverride_FallsBackPerPart.
Suites: Core 2539 / App 713 / UI 425 / Net 385 green.

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