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>
This commit is contained in:
parent
d4ff80cb2b
commit
3f34bca06f
3 changed files with 318 additions and 16 deletions
|
|
@ -0,0 +1,75 @@
|
|||
# `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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue