acdream/docs/research/2026-07-06-180-camera-sweep-strobe-handoff.md

9.8 KiB
Raw Blame History

Pickup handoff — #180 camera-sweep eye strobe (the #176 "stripes") — port retail's stateful sought-position

Paste the companion prompt (bottom of this file / the session log) into a fresh session. Read this file, then claude-memory/project_physics_collision_digest.md (top banner) + claude-memory/project_render_pipeline_digest.md (top banner), then ISSUES #180 (and #176's gate-2 note).

TL;DR — what #180 is

The user's #176 residual — "geometrical patterns, triangles, that get weird stripes… especially when I push the camera to the wall or the openings; if I zoom out and the camera does not touch the walls I get no pattern" — is NOT a render defect. The camera-collision sweep is BISTABLE while the compressed chase boom moves along a wall, and the per-frame view hard-cuts tear-interleave on screen:

[flap-sweep] (launch-176-cameye.log, frames ~3860738861): consecutive sweeps whose INPUT moved ~1.4 mm (the player's glide) flipped the OUTPUT 0.27 m along the boompulledIn 0.27 ↔ 0.53, eye (78.500,38.633,3.845) ↔ (78.669,38.815,3.938) — re-flipping every ~510 frames. All 368,357 sweeps returned ok=True (this is NOT the fallback path — the sweep itself has two first-contact solutions at an r±ε graze on the corridor's double-faced slabs, the #137 threshold family, camera edition). At ~1700 fps with no vsync, each monitor refresh interleaves scanline slices of BOTH views → the fine stripe/hatch patterns (worst near seams where the 0.27 m parallax is largest), flickering with movement. Parked camera = static (all prior "camera stable" evidence was parked-camera; the strobe needs motion + a compressed boom).

This very plausibly also produced the ORIGINAL #176 seam flashes (one of the two alternating eyes sees the purple under-room parallax, the other doesn't).

Eliminated for the stripes (DO-NOT-RETRY — all isolation-killed)

Ruled OUT How
Texture / grout-line interaction ACDREAM_LIGHT_DEBUG=3 (raw vLit field, texture ignored) still shows the pattern
The per-cell shell clip trim (gl_ClipDistance) ACDREAM_CLIP_DEBUG=1 (shells drawn WHOLE, slots forced 0) — user: "Not gone, still there"
Coplanar dat geometry 2 cm-epsilon sweep: corridor floor (z=6) clean; the only stack is the z=12 under-hall quad×4 — SAME-mesh, bit-identical depth ⇒ stable winner, cannot moiré
Shell double-instance / floor-coincident entity / portal seals [seam-*] probe: n=1 per cell at the lifted z; portal entities at z=12.05; zero depth fans (sealed dungeon)
Light-set churn under movement [seam-blk]: the floor's applied set held the SAME 8 identities through a 148k-frame session incl. ~12k distinct positions
The light pool tracking the camera FIXED d8984e87 (resident collection, player anchor) — verified: flood churned 8→41 across a full turn, set constant. Do NOT re-open; re-gate VISUALLY only after #180
SweepEye itself misporting update_viewer Read side-by-side: flags 0x5c, AdjustPosition seating, fallbacks all match 0x00453ce0. The bistability is in the transition's contact solution, not the port shape

The retail anchor (the fix direction — read BEFORE coding)

Retail's camera target is STATEFUL; ours re-demands the full boom every frame:

  • SmartBox per-frame (pseudo-C line ~91842, addr 0x00452d75): CameraManager::UpdateCamera(this->camera_manager, &ret, &this->viewer) — the argument is the CURRENT swept viewer, and the RETURN is assigned to this->viewer_sought_position (0x00452d84). So the next frame's sweep target derives from the collided position — the target CONVERGES onto the wall contact and re-extends gradually; the knife-edge full-length ray is not re-rolled per frame. That is why retail's camera glides along walls instead of strobing.
  • CameraManager::UpdateCamera = 0x00456660 (pseudo-C line ~95505). Only the head was read this session (offset-movement integration, pivot query); the sought-position derivation in the tail is UNREAD — read it fully, write the pseudocode doc (workflow step 3), then port.
  • SmartBox::update_viewer = 0x00453ce0 (line ~92761) — already faithfully ported by PhysicsCameraCollisionProbe.SweepEye; leave it alone.
  • No damping band-aid on the swept result without the UpdateCamera reading (the no-workaround rule; the user has already reverted redesigns that replaced retail mechanisms).

Port site: src/AcDream.App/Rendering/RetailChaseCamera.cs (the desired-eye / boom derivation) — it must consume the previous swept viewer per retail.

Also open from this arc

  • Site-A weenie light-registration LEAK: [seam-ent] showed one portal cell's I100 light ×2 → ×4 within a session — re-CreateObject re-registers the light under a fresh entity.Id (GameWindow ~3679: Id = _liveEntityIdCounter++) without tearing down the previous registration. The wash intensifies over time. Fix with #180's gate or fold into A7 (noted in ISSUES #176).
  • #176 re-gate happens only after #180 lands: camera pressed into walls/openings while moving → no patterns; corridor seam turn → steady purple wedge (the faceted SHAPE is cdb-proven retail — only instability is a defect).
  • After BOTH pass: strip the throwaway probes in one cleanup commit — [seam-*] (ACDREAM_PROBE_SEAMDRAW), ACDREAM_LIGHT_DEBUG modes, ACDREAM_CLIP_DEBUG. Keep [flap-sweep] (it rides ACDREAM_PROBE_FLAP).

Apparatus (reuse, don't rebuild)

  • ACDREAM_PROBE_FLAP=1[flap-sweep] per-sweep line: desiredBack/eyeBack/ pulledIn/in=/out= — THE #180 instrument (plus [flap]/[flap-cam]).
  • ACDREAM_PROBE_PVINPUT=1 → per-frame [pv-input] eye= at 6 dp.
  • The autonomous visual loop (no user needed, works where RenderDoc can't — RenderDoc doesn't support GL_ARB_bindless_texture and the modern gate throws):
    1. Launch via dotnet run (background) with probes; wait ~12 s.
    2. Input: keybd_event with real scan codes + KEYEVENTF_EXTENDEDKEY (GLFW decodes scancodes; vk-only silently does nothing). Arrows: Left 0x25/0x4B, Right 0x27/0x4D, Down 0x28/0x50. SetForegroundWindow first, verify with GetForegroundWindow. Hold-key = down, Sleep, up.
    3. Capture: GetWindowRect + Graphics.CopyFromScreen → PNG in the scratchpad → the Read tool renders it (take 3 captures ~150 ms apart — frame-pair diffs expose per-frame strobes).
    4. Repro recipe: back the character into a wall (hold Down ~2.5 s) — the boom compresses; the strobe fires DURING/just after motion near the wall.
    5. Close gracefully via CloseMainWindow() (ACE session clears in ~5 s).
  • Launch logs on disk (worktree root): launch-176-cameye.log (THE strobe capture), launch-176-user-gate.log (148k-frame user session incl. the light leak), launch-176-seamdraw.log, launch-176-fix-verify.log.
  • ACDREAM_LIGHT_DEBUG=1/2/3 + ACDREAM_CLIP_DEBUG=1 — shader/clip isolation toggles (throwaway; strip at cleanup).

State

  • main = worktree branch claude/vigorous-joliot-f0c3ad = 6aabe0b5 (lighting fix d8984e87 + isolation apparatus 6aabe0b5). Suites green (Core 2599+2skip / App 726+2skip / UI 425 / Net 385).
  • Register: AP-85 rewritten (resident+player-anchored pool; residual = single 128 pool vs retail 7-dyn/40-static). The a7 pseudocode doc carries a correction banner (visible_cell_table = RESIDENT registry — the identifier-name-≠-semantics lesson, memory feedback_bn_decomp_field_names class 4).
  • A client may be running — the user manages its lifecycle; ASK before rebuilding if the App DLL is locked (MSB3027 = lock, not code).
  • The test char spawns in the Facility Hub corridor (cell 0x8A020164).

Companion prompt (paste into the fresh session)

Fix #180 — the camera-collision sweep strobes the eye 0.27m per few frames when
the compressed chase boom moves along walls; at ~1700fps the tear-interleaved
views ARE the #176 "stripes/triangles" the gates keep failing on. The mechanism
is probe-pinned; every render suspect is isolation-killed. Do NOT re-investigate
the render, the light pool (fixed d8984e87, data-verified), or SweepEye's port
(faithful, exonerated).

READ FIRST, in order:
1. docs/research/2026-07-06-180-camera-sweep-strobe-handoff.md (evidence,
   DO-NOT-RETRY table, retail anchors, the autonomous visual-loop recipe)
2. claude-memory/project_physics_collision_digest.md (top banner)
3. docs/ISSUES.md #180 (+ #176's gate-2 note)

THE FIX = port retail's STATEFUL camera target: SmartBox (0x00452d75) feeds the
CURRENT swept viewer into CameraManager::UpdateCamera (0x00456660) and assigns
the RETURN to viewer_sought_position (0x00452d84) — the target converges onto
the wall contact instead of re-rolling the full knife-edge boom ray per frame
like RetailChaseCamera does. Workflow: read UpdateCamera's tail (the sought
derivation — UNREAD so far, pseudo-C line ~95505), write the pseudocode doc,
port into RetailChaseCamera. NO damping band-aids on the swept result.

Verify autonomously with the handoff's visual loop (keybd_event scan-codes +
GDI window capture + [flap-sweep]): back into a wall, confirm consecutive-frame
eye deltas stay continuous and no stripe patterns in captures. Then the user
gate covers #180 AND the #176 re-gate (steady purple wedge at the seam — the
wedge SHAPE is retail, cdb-proven). After both pass: fix the site-A light-
registration leak (one portal light stacked ×4/session — re-CreateObject
re-registers under fresh entity ids) and strip the throwaway probes
([seam-*], ACDREAM_LIGHT_DEBUG, ACDREAM_CLIP_DEBUG) in a cleanup commit.

State: main @ 6aabe0b5, suites green (Core 2599+2skip). Test char spawns in the
Facility Hub corridor 0x8A020164. A client may be running — the user manages
its lifecycle; ASK before rebuilding if the App DLL is locked.