acdream/docs/research/2026-07-06-viewer-step-subdivision-pseudocode.md
Erik 3f34bca06f port #181: retail viewer step subdivision (calc_num_steps 0x0050a0b0) - radius-anchored steps + remainder final step + viewer-exempt small-offset abort
The user's retail axiom (camera rock steady pressed into walls) vs our measured wall-press wander (~0.5mm/frame limit cycle, headless pin Issue181WallPressEquilibriumTests) sent us back to the decomp. Ghidra (clean, vs the BN x87 mush): retail VIEWERS subdivide the sweep into EXACTLY radius-length steps anchored at the start (offsetPerStep = offset*r/len, numSteps = floor(len/r)+1) with the final step recomputed mid-loop as the exact remainder (find_transitional_position 0x0050bdf0), and the negligible-offset abort is NON-viewer-only. Ours used ceil equal-slices for everything and aborted viewers too. Ported faithfully (pseudocode docs/research/2026-07-06-viewer-step-subdivision-pseudocode.md); non-viewer stepping already matched (TRANSITIONAL_PERCENT_OF_RADIUS=1.0).

Measurement: the wall-press limit cycle is UNCHANGED by the port (537.8um avg; a bit-exact 12-frame cycle: ~130um/frame inward creep x11 then a 2.6mm snap). With adjust_to_plane + adjust_sphere_to_poly now also Ghidra-verified faithful, the residual mm cycle is likely retail-class plateau physics - invisible at retail's 60fps vsync, tear-interleaved into visible stripes at our ~1500fps unsynced. The decisive user test: VSync ON (Settings/F11). Fallback discriminator: cdb-trace retail's viewer at a wall press. Suites green (Core 2600 / App 733 / UI 425 / Net 385).

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

75 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# `CTransition::calc_num_steps` + the viewer step loop — pseudocode (#181 equilibrium fix)
Source: Ghidra decompile (patchmem project, PDB-named) of `calc_num_steps`
(`0x0050a0b0`) and `find_transitional_position` (`0x0050bdf0`), read 2026-07-06
after the user's retail axiom ("retail camera is rock steady" pressed into
walls) contradicted acdream's measured wall-press wander (~0.5 mm/frame
forever, headless pin `Issue181WallPressEquilibriumTests`). The BN pseudo-C of
the same functions is x87 mush; Ghidra's is clean.
## `calc_num_steps` — 0x0050a0b0
```
calc_num_steps(this, out offset, out offsetPerStep, out numSteps):
if begin_pos == null: offset = 0; offsetPerStep = 0; numSteps = 1; return
offset = get_offset(begin_pos, end_pos)
r = local_sphere.radius
len = |offset|
if object_info.state & 4: # VIEWER (bit 2 of the 0x5c camera flags)
if len > F_EPSILON: # 2e-4
offsetPerStep = offset * (r / len) # EXACTLY radius-length steps, start-anchored
numSteps = floor(len / r) + 1 # the end lands INSIDE the last step
else:
offsetPerStep = 0; numSteps = 0 # zero-length path → the no-step success path
return
# non-viewer: equal slices of ~radius (TRANSITIONAL_PERCENT_OF_RADIUS = 1.0, 0x007c6874)
steps = len / (TRANSITIONAL_PERCENT_OF_RADIUS * r)
if steps > 1:
numSteps = ceil(steps); offsetPerStep = offset / numSteps
elif offset == 0:
offsetPerStep = 0; numSteps = 0
else:
offsetPerStep = offset; numSteps = 1 # short move → one whole step
```
## The step loop's viewer handling — `find_transitional_position` 0x0050bdf0
```
for i in 0 .. numSteps-1:
# VIEWER LAST-STEP REMAINDER: the final step covers exactly what's left,
# so the swept path ends exactly at end_pos (no overshoot):
if (state & 4) and i == numSteps-1 and len > F_EPSILON:
offsetPerStep = offset * ((len - (numSteps-1)*r) / len)
global_offset = adjust_offset(offsetPerStep)
# The negligible-offset abort is NON-VIEWER-ONLY — a pressed camera keeps
# stepping through sub-epsilon adjusted offsets:
if (state & 4) == 0 and |global_offset|² < F_EPSILON²: break
... check_pos += global_offset; transitional_insert(3); validate ...
if collision_normal_valid and (state & 8): break # PathClipped first-hit stop
```
## Why this stabilizes the wall-press equilibrium (#181)
acdream's pre-fix stepping for ALL movers was `numSteps = ceil(len/r)` equal
slices. The camera's convergence loop (sought = lerp(viewer→desired) → sweep →
viewer) perturbs `len` by mm every frame:
- equal slices: every step boundary shifts with `len`, and at `len` near an
exact multiple of r (the measured press pose: 1.5 m = 5 × 0.3 m) the step
COUNT flaps ceil-wise, teleporting the colliding step's window ~5 cm — the
clip's committed point jumps mm-scale, and the loop orbits a limit cycle
instead of a fixed point (the measured ~0.5 mm/frame wander → the #181
flicker excitation);
- retail viewer grid: the first `floor(len/r)` steps are constant radius-length
increments anchored at the pivot — invariant under mm target drift — and only
the remainder step breathes. The colliding step's window is stable, the clip
result is stable, the loop reaches retail's fixed point ("rock steady").
Port sites: `Transition.FindTransitionalPosition` (TransitionTypes.cs) — the
subdivision block + the per-step loop (last-step remainder + the abort gate).
Non-viewer behavior unchanged (already matches).