fix(physics): AD-55 retired — Sledding fast-sled constant is cos(10 deg)
Per docs/research/2026-07-30-ts4-116-oracle-plan.md Addendum (byte-proven 2026-07-30). Raw bytes of CPhysicsObj::calc_friction @ 0x0050ee70's Sledding fast-sled branch (0x0050ef52-0x0050ef6a): d9 86 38 01 00 00 fld dword [esi+0x138] ; contact_plane.Normal.Z dd 05 28 6b 7c 00 fld qword [0x007c6b28] ; = 0.17453292519943295 (10 deg RADIANS) d9 ff fcos ; st0 = cos(10 deg) = 0.984807753 de d9 fcompp confirm a genuine fcos opcode over a real 10-degrees-in-radians double literal -- not a BN misdecompile of a raw float load. Retail truly computes cos(10 deg) ~ 0.9848078 at runtime; ACE's 0.99999536f equals cos(0.1745 DEGREES) -- the same radian literal evaluated in degree mode, a proven ACE porting error carried into this port provisionally. PhysicsBody.calc_friction's Sledding near-flat override now compares GroundNormal.Z > 0.98480775f (cos 10 deg). Feel impact: retail's 0.2f fast-sled friction override engages on any ground within 10 degrees of flat; the old constant engaged only within ~0.175 degrees (never, in practice). Tests: two new boundary pins (calc_friction_sledding_fast_override_engages_at_5_degrees_from_flat / ..._does_not_engage_at_15_degrees_from_flat) construct a tilted GroundNormal with velocity purely orthogonal to the tilt plane (dot=0 exactly, isolating the Sledding-band friction value from the outer 0.25f gate and the normal-removal step) and assert the exact pow(1-friction, dt) decay on each side of the new 10-degree boundary. Register: AD-55 retired (struck through, retirement note with the byte decode). Full AcDream.Core.Tests suite: 4063 passed / 1 skipped, no regressions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0149220506
commit
252e806804
3 changed files with 75 additions and 14 deletions
|
|
@ -62,13 +62,13 @@ accepted-divergence entries (#96, #49, #50).
|
|||
|
||||
---
|
||||
|
||||
## 2. Adaptation (AD) — 47 rows (AD-56 filed 2026-07-30 at the Campaign P final physics slice, split out of the retired TS-4 row — the narrow pure-vertical (zero horizontal velocity) steep-roof-landing freeze, confirmed retail-identical; AD-25 retired 2026-07-30 at Campaign P Slice P3 — the remote dead-reckoning post-resolve now calls the exact ported `PhysicsObjUpdate.HandleAllCollisions` (the same function the local player and every ordinary body already use) instead of its own hand-inlined, narrower reflect gate; the row's own premise ("the remote DR sweep hasn't been rebuilt yet") no longer holds; AD-55 filed 2026-07-30 at Campaign P Slice P2, split out of the retired AP-7 row — the open cos(10°)-vs-0.99999536f Sledding slope-flatness constant; AD-53/AD-54 filed the same slice, split out of the retired TS-1 row — CliffSlide's three-source reference-normal fallback chain and the walkable-steepness reroute to CliffSlide; AD-52 filed 2026-07-29 at Campaign N slice N6 — the fragment-assembler 60 s partial TTL + completed-sequence ring; AD-51 filed 2026-07-29 at Campaign N slice N4 — the reclaimed-word pool for ACE's fresh-sequence cleartext RejectRetransmit; AD-50 filed 2026-07-29 at Campaign N slice N2 — the inbound-watermark ACE init; AD-49 stays reserved for Campaign N §5's blob-layer ordering deferral, filed when its slice lands; AD-47 and AD-48 filed 2026-07-29 at Campaign V slice V11 — the MSAA sample-position and present-pacing rows the campaign's risk register scheduled for the GL deletion; AD-11 retired 2026-07-23 — exact low-bit ItemUses predicate; AD-31 retired 2026-07-15 — the DAT-authored portal-space viewport replaces the black transit cover)
|
||||
## 2. Adaptation (AD) — 47 rows (AD-56 filed 2026-07-30 at the Campaign P final physics slice, split out of the retired TS-4 row — the narrow pure-vertical (zero horizontal velocity) steep-roof-landing freeze, confirmed retail-identical; AD-55 retired the same slice — the cos(10°) sled-flatness constant, byte-proven against `0x0050ee70`; AD-25 retired 2026-07-30 at Campaign P Slice P3 — the remote dead-reckoning post-resolve now calls the exact ported `PhysicsObjUpdate.HandleAllCollisions` (the same function the local player and every ordinary body already use) instead of its own hand-inlined, narrower reflect gate; the row's own premise ("the remote DR sweep hasn't been rebuilt yet") no longer holds; AD-53/AD-54 filed the same slice, split out of the retired TS-1 row — CliffSlide's three-source reference-normal fallback chain and the walkable-steepness reroute to CliffSlide; AD-52 filed 2026-07-29 at Campaign N slice N6 — the fragment-assembler 60 s partial TTL + completed-sequence ring; AD-51 filed 2026-07-29 at Campaign N slice N4 — the reclaimed-word pool for ACE's fresh-sequence cleartext RejectRetransmit; AD-50 filed 2026-07-29 at Campaign N slice N2 — the inbound-watermark ACE init; AD-49 stays reserved for Campaign N §5's blob-layer ordering deferral, filed when its slice lands; AD-47 and AD-48 filed 2026-07-29 at Campaign V slice V11 — the MSAA sample-position and present-pacing rows the campaign's risk register scheduled for the GL deletion; AD-11 retired 2026-07-23 — exact low-bit ItemUses predicate; AD-31 retired 2026-07-15 — the DAT-authored portal-space viewport replaces the black transit cover)
|
||||
|
||||
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|
||||
|---|---|---|---|---|---|
|
||||
| AD-53 | `Transition.CliffSlide`'s reference-normal cross-product operand tries THREE sources in priority order (`LastWalkablePlane` if `Normal.Z >= FloorZ`, then `LastKnownContactPlane` at the same threshold, then world-up `UnitZ`); retail's `CTransition::cliff_slide` uses `this->collision_info.last_known_contact_plane.N` directly, with no fallback chain | `src/AcDream.Core/Physics/TransitionTypes.cs` (`CliffSlide`, the `referenceNormal`/`refSource` selection above the cross-product) | Filed 2026-07-30 splitting TS-1's retirement (Campaign P Slice P2). A fresh read of `last_known_contact_plane`'s own maintenance (pc:272659-272668) confirms retail overwrites it unconditionally from `contact_plane` every `validate_transition` pass — including with a steep plane — so retail keeps NO separately-preserved flat-ground history there either; this fallback chain is a genuine acdream invention, not a retail-matching read. Kept because it compensates for AP-4's incomplete `OnWalkable` bookkeeping (L.4-cliffslide-fallback, 2026-04-30): without it, `cross(currentSteep, lastKnownSteep)` degenerates to zero after >1 frame on a continuous steep slope, and CliffSlide returns `OK` (no deflection) instead of downhill drift — the "stay on the roof" wedge the L.4 session fought | If AP-4's `OnWalkable` reordering is ever completed/removed, `last_known_contact_plane` should carry the same information retail's does and this fallback chain becomes unneeded ballast (or, worse, silently picks a stale `LastWalkablePlane` over the now-correct current one) — re-audit together with AP-4 | `CTransition::cliff_slide` pc:272397 (0050a6d0); `last_known_contact_plane` maintenance pc:272659-272668 (~0050ad07); `docs/research/2026-07-30-response-layer-edge-family-pseudocode.md` §2 gap #2 |
|
||||
| AD-54 | `Transition.EdgeSlideAfterStepDownFailed` reroutes to `CliffSlide` instead of `PrecipiceSlide` when the stored walkable polygon itself is steeper than `FloorZ` (`sp.WalkablePlane.Normal.Z < PhysicsGlobals.FloorZ`); retail's raw `SPHEREPATH::edge_slide` has no steepness branch here — `if (walkable != null) { ... precipice_slide(...) }` unconditionally | `src/AcDream.Core/Physics/TransitionTypes.cs` (`EdgeSlideAfterStepDownFailed`, the `L.4-walkable-steep` block) | Filed 2026-07-30 splitting TS-1's retirement (Campaign P Slice P2). The permissive `LandingZ` walkable-acceptance threshold that lets a steep roof become "walkable" in the first place IS confirmed retail-faithful (`BSPTREE::find_collisions` unconditional `walkable_allowance = LandingZ`, pc:323740-323783, TS-4's own citation) — so a steep-roof walkable polygon is a real state retail also reaches. What is NOT independently verified is whether retail's outer `transitional_insert` caller absorbs a same-polygon-standing `COLLIDED_TS` from `precipice_slide` (its raw `find_crossed_edge` returning false while standing on, not crossing, the polygon) some other way that avoids the acdream "stuck in a Collided revert loop" this reroute prevents | If retail's outer retry loop turns out to already handle the no-crossed-edge-while-standing-on-a-steep-poly case without a reroute, this branch is an unnecessary compensating layer that could route a genuinely PrecipiceSlide-bound case (a shallow polygon edge that happens to sit at exactly `FloorZ`) into CliffSlide instead | `SPHEREPATH::edge_slide` pc:273001-273090 (0050b3d0, direct walkable branch quoted at pc:364-370 in the P2 research doc); `BSPTREE::find_collisions` pc:323740-323783 (0053a730, unconditional `LandingZ`); `docs/research/2026-07-30-response-layer-edge-family-pseudocode.md` §2 gap #3 |
|
||||
| AD-55 | `calc_friction`'s Sledding slope-flatness test compares `GroundNormal.Z > 0.99999536f` (≈0.175° from flat); the raw retail decomp literally computes `__fcos(0.17453292519943295)` (= cos(10°) ≈ 0.984808) and compares that against `contact_plane.N.z` — physically very different tests (0.175° accepts only essentially-perfectly-flat ground; 10° accepts any modest slope) | `src/AcDream.Core/Physics/PhysicsBody.cs` (`calc_friction`, the Sledding near-flat branch) | Filed 2026-07-30 splitting AP-7's retirement (Campaign P Slice P2). Two hypotheses, neither confirmed this pass: (a) BN misdecompiled a raw float-constant load as an `__fcos()` call (a known BN artifact class), or (b) ACE's own port made an independent error and cos(10°) is correct. `0.99999536f` is kept provisionally — least churn, since it is what acdream's own prior (structurally unreachable) dead code already had — pending a live Ghidra decompile of `0050ee70` checking whether the FCOS opcode is real or a raw `FLD` of one of these two constants | Currently harmless in production: nothing sets `PhysicsState.Sledding` client-side (see #166 research), so this branch is unreachable either way. The moment a data-authored Sledding toggle exists, the wrong constant changes which slopes get the light 0.2f sled-friction override vs. the heavier default | `CPhysicsObj::calc_friction` pc:276694-276822 (0050ee70), the `__fcos(0.17453292519943295)` slope-flatness comparison; ACE `PhysicsObj.calc_friction` PhysicsObj.cs:2120-2141 (`0.99999536f`); `docs/research/2026-07-30-response-layer-edge-family-pseudocode.md` §1, §7 item 3 |
|
||||
| ~~AD-55~~ | **RETIRED 2026-07-30 (Campaign P final physics slice) — byte-proven.** Raw bytes of `CPhysicsObj::calc_friction @ 0x0050ee70`'s Sledding fast-sled branch (`0x0050ef52-0x0050ef6a`) show a genuine `fcos` opcode over the real 10°-in-radians double literal `0.17453292519943295` at `0x007c6b28` — not a BN misdecompile of a raw float load. Retail truly computes `cos(10°) ≈ 0.9848078` at runtime; ACE's `0.99999536f` is `cos(0.1745 DEGREES)`, the same literal evaluated in degree mode — a proven ACE porting error. `calc_friction` now compares `GroundNormal.Z > 0.98480775f`. | `src/AcDream.Core/Physics/PhysicsBody.cs` (`calc_friction`, the Sledding near-flat branch); `tests/AcDream.Core.Tests/Physics/PhysicsBodyTests.cs` (`calc_friction_sledding_fast_override_engages_at_5_degrees_from_flat`, `..._does_not_engage_at_15_degrees_from_flat`) | — | — | `CPhysicsObj::calc_friction` pc:276694-276822 (0050ee70); raw byte decode `docs/research/2026-07-30-ts4-116-oracle-plan.md` Addendum |
|
||||
| AD-56 | A body falling PERFECTLY PLUMB (zero horizontal velocity) onto a steep-but-below-FloorZ polygon (LandingZ-permissive, e.g. a steep roof) freezes at its landing position forever once Path 6's steep-poly shortcut is removed (TS-4). `AdjustOffset`'s crease projection (`Cross(ContactPlane.Normal, SlidingNormal)`) against a purely-Z gravity offset is mathematically annihilated (`Dot(slideOffset, offset) = 0` exactly, since `slideOffset.Z = 0` and the offset is purely Z), tripping the abort-small-offset guard before `TransitionalInsert` can run again | `src/AcDream.Core/Physics/TransitionTypes.cs` (`AdjustOffset`'s crease-projection math, shared by every mover); pinned by `tests/AcDream.Core.Tests/Physics/Ts4SteepRoofWedgeCaptureTests.cs` (`FallOntoSteepSlope_PureVertical_FreezesAtDegenerateFixedPoint_RetailParity`) | Filed 2026-07-30 at the Campaign P final physics slice, split out of the retired TS-4 row. This is not a code bug: the mechanism is present identically in the raw retail decomp, ACE's port, and this port (`docs/research/2026-07-30-ts4-116-oracle-plan.md` §1.2 Step E, §1.3) — every one of the three references crushes a purely-vertical offset to zero the same way. A live player almost never produces this exact input: WASD, camera-relative movement, and even small numerical noise inject some horizontal component, which the SAME cross product does NOT annihilate (only a component exactly along the downhill/gravity line is removed) — confirmed by the decisive companion fixture `FallOntoSteepSlope_WithHorizontalVelocity_...`, which converges cleanly with a mere ±0.3 m/s residual horizontal component | A hypothetical mover that manages a truly zero-horizontal-velocity approach to a steep-but-LandingZ-permissive surface (a vertical-drop elevator platform, a scripted teleport landing) would freeze identically to retail; not reachable by ordinary player/NPC movement | `CTransition::adjust_offset` pc:272271-272393 (0x0050a370); `docs/research/2026-07-30-ts4-116-oracle-plan.md` §1.2 Step E, §1.3, §1.5 |
|
||||
| AD-46 | **LIVE. Reframed at Campaign V slice V11 (2026-07-29), when GL was deleted and the comparison that discovered this row ceased to exist.** Dense alpha-blended distant scenery (the treeline) may read slightly denser than retail's, because the anisotropic TAP PATTERN is implementation-defined and acdream's Vulkan driver does not tap identically to retail's D3D9 one. Both request the same sampler state — trilinear, clamp-and-repeat, the device's maximum anisotropy. **What changed at V11 is only the left-hand side of the comparison**: this was measured GL-vs-Vulkan (~15% of the pixels in the band), and it is now a Vulkan-vs-retail question against the D3D oracle in the last column. The measurement below is retained as the evidence that the residual is a tap pattern and not a bug, even though one of its two arms no longer exists. | `src/AcDream.App/Rendering/Wb/WorldTextureArray.cs` (`RhiWorldTextureArray.WorldArrayAnisotropy`); measured in plan §5.5.19, reframed §5.5.24 | Not assumed — narrowed by measurement while both backends still existed, on an offline capture with no session, no entities and both clocks pinned. Anisotropy 1 → 41,509 differing pixels in the tree band; anisotropy 16 (GL's value, and retail's `m_D3DCaps.MaxAnisotropy`) → 22,266, and the rest of the frame fell to 497 px of 563,200, i.e. 8.8e-04, inside the campaign's 0.001 threshold. The residual was not a sub-pixel shift (an integer shift search found none), not a sharpness change (high-frequency energy matched within 5%), and not depth precision (forcing Vulkan's window-depth range to GL's compressed [0.5, 1] moved it by 3%). Monotone improvement toward GL's own anisotropy with no knob left is what made it a driver property rather than a bug. | Distant foliage shimmers or reads denser than retail's. The class is confined to alpha-blended dense overlap: opaque terrain, roofs, walls, water, statics, the character and the whole retained UI are inside threshold. **Now unfalsifiable by self-differential** — with GL gone, the only way to retire this row is a side-by-side against the retail client, not against another acdream backend. | `RenderDeviceD3D::SetDefaultD3DStates @ 0x005a3800`, whose `SetSamplerState(stage, 0xA /* D3DSAMP_MAXANISOTROPY */, m_D3DCaps.MaxAnisotropy)` at `0x005a4230` is the value acdream requests |
|
||||
| AD-47 | **Filed at Campaign V slice V11 (2026-07-29); the campaign's risk register scheduled this row here.** Multisample resolve sample POSITIONS are unspecified by both the Vulkan and D3D9 specifications, so acdream's MSAA-on silhouette edges do not match retail's pixel-for-pixel even at the same sample count. acdream's strict pixel gates therefore run with MSAA forced OFF on every arm, and MSAA-on gets only a relaxed visual smoke. | `src/AcDream.App/RuntimeOptions.cs` (`ACDREAM_MSAA_SAMPLES`); forced to 0 in `tools/run-offline-pixel-gate.ps1` | Measured, not assumed: plan §5.5.16 compared two backends at 4x and found **8.83% of the frame differing — 81,359 px of 921,600 — essentially all of it hugging foliage and silhouette edges**, which is ninety-fold over the 0.001 gate threshold. That is two implementations' sample patterns, not a renderer divergence, which is why forcing MSAA off is what makes the remaining difference attributable rather than a threshold relaxation. | Edge quality on thin geometry (fence rails, foliage, distant railings) differs from retail at the sub-pixel level whenever MSAA is on, which is the ordinary player configuration. Because the gates run MSAA off, **a real regression confined to the multisample path would not be caught by them** — that is the actual exposure this row records. | D3D9 `D3DRS_MULTISAMPLEANTIALIAS` / `D3DMULTISAMPLE_TYPE` as set by `RenderDeviceD3D::SetDefaultD3DStates @ 0x005a3800`; retail's sample pattern is the driver's, exactly as ours is |
|
||||
|
|
|
|||
|
|
@ -642,17 +642,24 @@ public sealed class PhysicsBody
|
|||
/// GroundedRootMotion_FrictionThreshold_DoesNotHammerLocomotionTests for
|
||||
/// the regression pin on the root-motion path specifically.
|
||||
///
|
||||
/// ⚠️ Constant discrepancy (Ghidra-verify, NOT resolved this pass): the
|
||||
/// raw decomp's Sledding slope-flatness test literally computes
|
||||
/// __fcos(0.17453292519943295) (= cos(10°) ≈ 0.984808) and compares
|
||||
/// against contact_plane.N.z; ACE's port instead compares
|
||||
/// ContactPlane.Normal.Z > 0.99999536f directly (≈0.175° from flat, NOT
|
||||
/// 10°) — physically very different tests. Neither hypothesis (a BN
|
||||
/// misdecompile of a raw float load as __fcos, vs. an independent ACE
|
||||
/// port error) is confirmed; a live Ghidra decompile of 0050ee70 settles
|
||||
/// it. 0.99999536f is kept provisionally (least churn — it's what
|
||||
/// acdream's own prior dead code already had); do not silently resolve
|
||||
/// this without the Ghidra check. See register row AD-55.
|
||||
/// AD-55 RESOLVED (Campaign P final physics slice, 2026-07-30; byte
|
||||
/// decode in docs/research/2026-07-30-ts4-116-oracle-plan.md Addendum).
|
||||
/// Raw bytes of <c>CPhysicsObj::calc_friction @ 0x0050ee70</c>'s
|
||||
/// Sledding fast-sled branch (0x0050ef52-0x0050ef6a):
|
||||
/// <code>
|
||||
/// d9 86 38 01 00 00 fld dword [esi+0x138] ; contact_plane.Normal.Z
|
||||
/// dd 05 28 6b 7c 00 fld qword [0x007c6b28] ; = 0.17453292519943295 (10 deg in RADIANS)
|
||||
/// d9 ff fcos ; st0 = cos(10 deg) = 0.984807753
|
||||
/// de d9 fcompp
|
||||
/// </code>
|
||||
/// This is a genuine <c>fcos</c> opcode over a real 10°-in-radians
|
||||
/// double literal — not a BN misdecompile of a raw float load. Retail
|
||||
/// truly computes <c>cos(10°) ≈ 0.9848078</c> at runtime and compares
|
||||
/// <c>Normal.Z</c> against it. ACE's <c>0.99999536f</c> equals
|
||||
/// <c>cos(0.1745 DEGREES)</c> — the same radian literal evaluated in
|
||||
/// degree mode, a proven ACE porting error, not a BN artifact.
|
||||
/// Replaced with the byte-confirmed <c>0.98480775f</c> (cos 10°).
|
||||
/// Register row AD-55 retired in the same commit.
|
||||
/// </summary>
|
||||
public void calc_friction(float dt, float velocityMag2)
|
||||
{
|
||||
|
|
@ -674,7 +681,7 @@ public sealed class PhysicsBody
|
|||
{
|
||||
if (velocityMag2 < 1.5625f) // 1.25² — slow sled
|
||||
friction = 1.0f;
|
||||
else if (velocityMag2 >= 6.25f && GroundNormal.Z > 0.99999536f) // near-flat, Ghidra-verify (see doc comment)
|
||||
else if (velocityMag2 >= 6.25f && GroundNormal.Z > 0.98480775f) // cos(10°), byte-confirmed (AD-55, see doc comment)
|
||||
friction = 0.2f;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -555,6 +555,60 @@ public sealed class PhysicsBodyTests
|
|||
$"got speed {body.Velocity.Length()}");
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// AD-55 (Campaign P final physics slice, 2026-07-30): Sledding
|
||||
// fast-sled slope-flatness constant is cos(10 deg) ~ 0.98480775f
|
||||
// (byte-confirmed, docs/research/2026-07-30-ts4-116-oracle-plan.md
|
||||
// Addendum), NOT the ACE-derived 0.99999536f this port carried
|
||||
// provisionally (cos(0.1745 DEGREES) -- a radians/degrees porting
|
||||
// bug). Both boundary tests put Velocity purely along Y and
|
||||
// GroundNormal's tilt purely in the X-Z plane, so
|
||||
// dot(velocity, groundNormal) is EXACTLY zero -- the outer 0.25f gate
|
||||
// and the normal-component-removal step are inert, isolating the
|
||||
// Sledding-band friction value itself.
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void calc_friction_sledding_fast_override_engages_at_5_degrees_from_flat()
|
||||
{
|
||||
float cos5 = MathF.Cos(5f * MathF.PI / 180f);
|
||||
float sin5 = MathF.Sin(5f * MathF.PI / 180f);
|
||||
var body = MakeGrounded();
|
||||
body.GroundNormal = new Vector3(sin5, 0f, cos5); // 5 deg tilt -- within retail's real 10 deg band
|
||||
body.State |= PhysicsStateFlags.Sledding;
|
||||
body.Velocity = new Vector3(0f, 3f, 0f);
|
||||
float mag2 = body.Velocity.LengthSquared();
|
||||
const float dt = 1f / 60f;
|
||||
|
||||
body.calc_friction(dt, mag2);
|
||||
|
||||
// friction = 0.2f (light) expected: scalar = (1 - 0.2)^dt.
|
||||
float expectedSpeed = 3f * MathF.Pow(0.8f, dt);
|
||||
Assert.Equal(expectedSpeed, body.Velocity.Length(), precision: 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void calc_friction_sledding_fast_override_does_not_engage_at_15_degrees_from_flat()
|
||||
{
|
||||
float cos15 = MathF.Cos(15f * MathF.PI / 180f);
|
||||
float sin15 = MathF.Sin(15f * MathF.PI / 180f);
|
||||
var body = MakeGrounded();
|
||||
body.GroundNormal = new Vector3(sin15, 0f, cos15); // 15 deg tilt -- past retail's real 10 deg threshold
|
||||
body.State |= PhysicsStateFlags.Sledding;
|
||||
body.Velocity = new Vector3(0f, 3f, 0f);
|
||||
float mag2 = body.Velocity.LengthSquared();
|
||||
const float dt = 1f / 60f;
|
||||
|
||||
body.calc_friction(dt, mag2);
|
||||
|
||||
// friction stays the default 0.95f (heavy): the fast-sled override
|
||||
// must NOT engage past retail's real 10 deg threshold, unlike the
|
||||
// old (buggy) 0.99999536f/~0.175 deg constant which would also
|
||||
// have rejected this case for the wrong reason.
|
||||
float expectedSpeed = 3f * MathF.Pow(1f - PhysicsBody.DefaultFriction, dt);
|
||||
Assert.Equal(expectedSpeed, body.Velocity.Length(), precision: 3);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// update_object — per-frame driver
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue