docs(physics): #347 premise revision — retail may alternate too; ftp:edge ratio is the discriminator
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run

The cliff_slide arms are conformant in ACE, our port, and the bytes
(compare constant at 0x794610 verified 0.0), the round-1 slidn:edge
ratio (538:594) refutes a retail retry storm, and the user's
side-by-side speed observation fits alternation. Round-2 cdb script
now counts find_transitional_position; H-A (identical, retire AD-70)
vs H-B (within-tick yield) resolves on one ratio. The temporary
Scratch347 diagnostic test rides along until #347 closes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-07 13:42:14 +02:00
parent 9fc4cfbf59
commit 535f41bbdf
3 changed files with 121 additions and 2 deletions

View file

@ -120,3 +120,48 @@ Clean-room complete suite; dual Opus review (conformance byte-check of
cliff_slide's fchs arms + blast radius over every CliffSlide caller); cliff_slide's fchs arms + blast radius over every CliffSlide caller);
retire AD-70 in the same commit; user slope feel gate (~2 min: glide speed retire AD-70 in the same commit; user slope feel gate (~2 min: glide speed
now matches retail side-by-side; downhill/uphill/hover unchanged). now matches retail side-by-side; downhill/uphill/hover unchanged).
## PREMISE REVISION (2026-08-08, same session — before any code change)
Three findings force the "retail = full-rate within-tick" premise back to
OPEN:
1. **cliff_slide's arms are conformant in all three sources.** ACE
(`Transition.cs:242-266`), our port, and the byte decode agree on the
crease vector, the sign arms, and the compare-vs-0.0 (double at
0x794610, verified zero). For the conformance fixture's geometry those
arms move the check position INTO the face (the arming tick's probe
trace shows primary dist -0.056 -> -0.200 -> -0.487 across the three
attempts) — the "dig" is what the code as written does, in retail's
bytes too.
2. **The round-1 counters refute a per-tick retry storm in retail.**
slidn:edge = 538:594 ~ 1:1.1. Our alternation shape produces THREE edge
entries per sliding-normal set (the dig-retries); retail produces ~ONE.
The glide window in the counter progression (edge 0 -> 594 across six
1280-vwalk blocks, then frozen) also shows edge growth stopping the
moment the hold ended.
3. **The user's side-by-side observation** ("I cant detect any speed
change from retail", #345 gate, 2026-08-08) is consistent with retail
ALTERNATING exactly as we do — arm one tick, move the next — and
inconsistent with retail moving at double our rate.
Competing hypotheses now:
- **H-A (retail alternates too):** retail's arming tick runs ONE edge
entry (no retry after the cliff-slide Adjusted) and yields nothing; the
next tick's pre-projection moves. Then our ONLY divergence is the two
extra futile dig-retries per arming tick (invisible — the tick's output
is discarded), #347 closes as measured-identical, and AD-70 is RETIRED
as a wrong inference, not fixed.
- **H-B (retail yields within the tick):** the single edge entry precedes
a successful same-tick commit. Requires the retried/continued insert to
land clean — mechanism unknown given the dig direction — and would keep
AD-70 open as written.
**The discriminator is one number in the round-2 capture:**
`CTransition::find_transitional_position` (~1/tick for the player) vs
`edge_slide` during the glide window. H-A predicts ftp:edge ~ 2:1
(edge every other tick); H-B predicts ~ 1:1 (edge every tick). The
round-2 script (`tools/cdb/345-glide-stacks.cdb`) now counts ftp, prints
the periodic progression, samples 6 stacks each for edge/cliff/step_down,
and auto-detaches at 300 edge hits via the fall-through-then-top-level-qd
recipe. NO code changes until this capture runs.

View file

@ -0,0 +1,72 @@
using System;
using System.IO;
using System.Numerics;
using AcDream.Core.Physics;
using Xunit;
using Xunit.Abstractions;
namespace AcDream.Core.Tests.Physics;
// TEMPORARY #347 diagnostic — deleted before landing.
public sealed class Scratch347DiagTests
{
private readonly ITestOutputHelper _out;
public Scratch347DiagTests(ITestOutputHelper output) => _out = output;
[Fact]
public void DumpFirstArmingTick()
{
var heights = new byte[81];
heights[3 * 9 + 4] = 32;
var heightTable = new float[256];
for (int i = 0; i < 256; i++) heightTable[i] = i;
var engine = new PhysicsEngine();
engine.AddLandblock(0xA9B4FFFFu, new TerrainSurface(heights, heightTable),
Array.Empty<CellSurface>(), Array.Empty<PortalPlane>(), 0f, 0f);
var body = new PhysicsBody
{
State = PhysicsStateFlags.Gravity,
TransientState = TransientStateFlags.Active | TransientStateFlags.Contact | TransientStateFlags.OnWalkable,
};
float dx = 0f, dy = 0.23f;
var position = new Vector3(80.4f, 79.8f, 0f);
uint cell = TerrainSurface.ComputeOutdoorCellId(0xA9B4FFFFu, 80.4f, 79.8f);
PhysicsDiagnostics.DumpTransitFailEnabled = true;
var saved = Console.Out;
var sw = new StringWriter();
Console.SetOut(sw);
try
{
for (int tick = 0; tick < 3; tick++)
{
var result = engine.ResolveWithTransition(
currentPos: position,
targetPos: new Vector3(position.X + dx, position.Y + dy, position.Z),
cellId: cell,
sphereRadius: 0.47f,
sphereHeight: 1.20f,
stepUpHeight: 0.60f,
stepDownHeight: 1.50f,
isOnGround: true,
body: body,
moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
movingEntityId: 0x5000000Au);
position = result.Position;
cell = result.CellId;
if (tick < 2) sw.GetStringBuilder().Clear(); // keep only the arming tick
}
}
finally
{
Console.SetOut(saved);
PhysicsDiagnostics.DumpTransitFailEnabled = false;
}
foreach (var line in sw.ToString().Split('\n'))
_out.WriteLine(line.TrimEnd());
}
}

View file

@ -9,14 +9,16 @@ r $t2 = 0
r $t3 = 0 r $t3 = 0
r $t4 = 0 r $t4 = 0
r $t5 = 0 r $t5 = 0
r $t6 = 0
bp acclient!OBJECTINFO::validate_walkable "r $t0 = @$t0 + 1; .if (@$t0 < 100000) { gc }" bp acclient!OBJECTINFO::validate_walkable "r $t0 = @$t0 + 1; .if (@$t0 % 500 == 0) { .printf \"[c] vwalk=%d tins=%d edge=%d cliff=%d stepdown=%d stepup=%d ftp=%d\\n\", @$t0, @$t1, @$t2, @$t3, @$t4, @$t5, @$t6 }; .if (@$t0 < 100000) { gc }"
bp acclient!CTransition::transitional_insert "r $t1 = @$t1 + 1; gc" bp acclient!CTransition::transitional_insert "r $t1 = @$t1 + 1; gc"
bp acclient!CTransition::edge_slide "r $t2 = @$t2 + 1; .if (@$t2 <= 6) { .printf \"[edge_slide %d]\\n\", @$t2; kc 14 }; .if (@$t2 < 300) { gc }" bp acclient!CTransition::edge_slide "r $t2 = @$t2 + 1; .if (@$t2 <= 6) { .printf \"[edge_slide %d]\\n\", @$t2; kc 14 }; .if (@$t2 < 300) { gc }"
bp acclient!CTransition::cliff_slide "r $t3 = @$t3 + 1; .if (@$t3 <= 6) { .printf \"[cliff_slide %d]\\n\", @$t3; kc 14 }; gc" bp acclient!CTransition::cliff_slide "r $t3 = @$t3 + 1; .if (@$t3 <= 6) { .printf \"[cliff_slide %d]\\n\", @$t3; kc 14 }; gc"
bp acclient!CTransition::step_down "r $t4 = @$t4 + 1; .if (@$t4 <= 6) { .printf \"[step_down %d]\\n\", @$t4; kc 14 }; gc" bp acclient!CTransition::step_down "r $t4 = @$t4 + 1; .if (@$t4 <= 6) { .printf \"[step_down %d]\\n\", @$t4; kc 14 }; gc"
bp acclient!CTransition::step_up "r $t5 = @$t5 + 1; gc" bp acclient!CTransition::step_up "r $t5 = @$t5 + 1; gc"
bp acclient!CTransition::find_transitional_position "r $t6 = @$t6 + 1; gc"
g g
.printf "FINAL vwalk=%d tins=%d edge=%d cliff=%d stepdown=%d stepup=%d\n", @$t0, @$t1, @$t2, @$t3, @$t4, @$t5 .printf "FINAL vwalk=%d tins=%d edge=%d cliff=%d stepdown=%d stepup=%d ftp=%d\n", @$t0, @$t1, @$t2, @$t3, @$t4, @$t5, @$t6
qd qd