# Stair Dismount physics recovery and validation

This browser reconstruction implements the mechanics recovered from the supplied Porrasturvat 1.0.2 Windows executable. It uses historical ODE in double precision, rather than substituting a modern physics engine. Source and extraction scripts are included with the local project.

## Original engine identification

The July 27, 2002 ODE source at [commit e42b3fa17812e74fd5a8e4df42d438f6dec61bfc](https://github.com/odedevs/ode/tree/e42b3fa17812e74fd5a8e4df42d438f6dec61bfc) matches embedded assertion line numbers in three original engine files: `ode.cpp`, `geom.cpp` and `joint.cpp`. Nearby revisions can share those files, so this identifies a strong source match rather than uniquely identifying the shipped build.

The browser compiles that source to WebAssembly with 64-bit `dReal`, the original narrowphase and hash broadphase, and `dWorldStep`. Floating point contraction and fast-math are disabled.

## Recovered mechanics

| Setting | Original value |
| --- | --- |
| Gravity | Z up, gravity [0, 0, -10] |
| Constraint settings | ERP 0.99, CFM 0.01 |
| Physics step | 0.02 seconds |
| Game clock | 22 ms per physics tick |
| Contact limit | 50 per outer geometry pair |
| Contact surface | Infinite friction, bounce 0.5, bounce threshold 0.1 |
| Body count | 13, total mass 70.91 |
| Joints | 8 hinges, 4 ball joints |
| Stair geometry | 12 original stair boxes and the top platform |
| Run stopping | 20 seconds maximum, or 3.25 seconds after first ground contact, or 4 seconds after last qualifying impact |

The original XML model was extracted from the executable's CFL3 archive. Its masses, dimensions, positions, rotations and joint limits are preserved. The apparent capsule limbs use **box collisions and box inertia**. The head uses a sphere of radius 0.375. The ragdoll starts at model offset [-2, -2, 12.025].

The original keeps body geometries both individually and inside a geometry group in the same collision space. This duplicates certain contacts and scores. The port preserves that behavior and the callback's early return when it encounters an excluded adjacent body pair.

The joint-axis transform contains an original component-packing quirk: `408690` produces [x,y,z,w], while `407d80` consumes the result as scalar-first. The port implements those routines literally; a conventional Euler-to-quaternion replacement changes the fall substantially. The original degrees-to-radians literal is 0.017453292519943295. Push pitch is rounded to float32 and clamped using float32 ±π/2 before computing the force.

The shipped x87 control word selects 53-bit arithmetic precision. Expression order therefore matters: box inertia, quaternion multiplication, world inertia/gyroscopic torque, constraint products, collision projection/extents, and edge contact points now follow the executable's operation order. Scoring sums linear squared components in order (2+0)+1 and angular components (0+1)+2; body damage totals are accumulated in original ascending-ID order.

## Original scoring

At each body-versus-static contact, before the physics step:

```text
speed = length(linearVelocity) + length(angularVelocity)
excess = clamp(speed, 10, 25) - 10
damage += excess * bodyWeight * 50
```

Body-versus-body contacts do not score. Every qualifying static contact contributes independently, including the original group duplicates.

| Body | Weight |
| --- | --- |
| Head | 3 |
| Neck | 4 |
| Torso | 1.5 |
| Upper arms | 0.45 |
| Forearms | 0.35 |
| Thighs | 0.55 |
| Shins | 0.4 |
| Feet | 0.15 |

The displayed score follows `(4 * previousDisplay + totalDamage) * 0.2` each tick and rounds to the nearest integer. Damage heat fades by 0.03 per tick and increases by `excess * 0.06666666666666667` per contact, clamped to [0, 1].

## Original push and audio

The power meter is `abs(sin(smoothedTimeMs * 0.005))`. Clicking Dismount applies `(1 - meter * 0.5) * 12288` force to the selected body center for one physics step. Heading zero pushes toward positive Y, down the staircase. The visual power bar fills with `1 - meter`, so an empty bar still produces half of the maximum push force.

All 48 original impact samples and the music track are included unchanged. The original sound categories, sample selection table, per-sample volume, 250 ms category cooldown and MSVCRT random generator are reconstructed. Music loop points are 508032 and 3043432 samples at 44100 Hz. Browser audio starts when enabled through the controls.

## Runtime verification

The reference now executes the original x86 game body/joint constructors, push routine at `402490`, collision/scoring callback at `4051a0`, and timer at `403730` through its pre-render boundary. Model records, lookup maps and wall time are supplied by the harness; rendering and sound calls are suppressed. The original executable is never modified.

The unmodified-emulator reference and the canonical math diagnostic are saved separately. Unicorn's FSIN/FCOS/FPATAN implementations call host math routines rather than reproducing real Intel x87 transcendental instructions. Canonical mode substitutes only the hinge FPATAN at `419a37` with a 60-digit arctangent rounded to binary64. For the three angled cases, four original push sine/cosine instructions additionally use correctly rounded 90-digit results, explicitly labeled per case. The browser computes push trigonometry and hinge angles at extended precision before storing doubles. This diagnostic tests all remaining game and engine arithmetic; it does not establish hardware equivalence.

For the default full-force torso run, all 301 full physical states (62 values per body) and all finite values in 600 solver pre/post records match exactly in canonical mode. Separately, 2,691 original box/box collision inputs produce exact normals, depths, contact codes and positions in WASM. Randomized 500-case probes also confirm mass/inertia, quaternion products, transformed inertia and gyroscopic torque arithmetic.

The regression corpus covers all 13 targets at both force limits and three angled pushes. It checks independent selected snapshots and completion values. Signed zeros are excluded from JSON comparisons because JavaScript serializes them as zero. The deep solver comparison normalizes original unbounded limits to JSON null and compares all finite values exactly. The full default state comparison is a stronger additional check; selected snapshots do not prove that every intermediate tick in every input matches.

The canonical default score is 43,793 at tick 300; the unmodified emulator gives 43,305 at tick 300. Small angle-rounding differences can lead to different later collision branches. These are two explicitly different math modes, not a measured universal error bound. Real Windows/x86 gameplay recordings remain necessary for hardware-level parity.

The prior Python-callback oracle and its mirrored-trajectory/20,514-score results are retained as historical research only and superseded by this reference. Historical ODE emits recoverable LCP degeneracy diagnostics in some original and browser runs; the original fallback remains intact.

## Browser interface and remaining scope

The browser interface, camera views and lighting are new. The port supports body selection, heading and pitch, the original pulsing push control, pause and stepping, recorded replay, slow playback, per-body damage and browser-local personal bests. It preserves the one-physics-tick-per-render limit, the original double clock smoothing, and integer scheduler; a60-frame original timer trace verifies frame-rate changes and suppressed steps. The original splash/menu graphics and retired online score service are not reproduced.

Original game and model: Jetro Lauha / tAAt. Music: kArpo. Historical ODE: Russell L. Smith. The [author's technical notes](https://www.flipcode.com/archives/08-06-2003.shtml) corroborate the original use of ODE and ball/hinge joints. Original assets retain their supplied license; ODE is included under its BSD license.
