diff --git a/docs/research/2026-08-08-347-fix-contract.md b/docs/research/2026-08-08-347-fix-contract.md index 92226db0..c72b742d 100644 --- a/docs/research/2026-08-08-347-fix-contract.md +++ b/docs/research/2026-08-08-347-fix-contract.md @@ -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); 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). + +## 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. diff --git a/tests/AcDream.Core.Tests/Physics/Scratch347DiagTests.cs b/tests/AcDream.Core.Tests/Physics/Scratch347DiagTests.cs new file mode 100644 index 00000000..f8b12bf5 --- /dev/null +++ b/tests/AcDream.Core.Tests/Physics/Scratch347DiagTests.cs @@ -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(), Array.Empty(), 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()); + } +} diff --git a/tools/cdb/345-glide-stacks.cdb b/tools/cdb/345-glide-stacks.cdb index 94442936..18c97657 100644 --- a/tools/cdb/345-glide-stacks.cdb +++ b/tools/cdb/345-glide-stacks.cdb @@ -9,14 +9,16 @@ r $t2 = 0 r $t3 = 0 r $t4 = 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::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::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::find_transitional_position "r $t6 = @$t6 + 1; gc" 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