{
  "summary": "Deep, adversarially-verified comparison of holtburger player-movement physics vs decompiled retail AC / ACE",
  "agentCount": 64,
  "logs": [
    "Deep-diving 7 physics dimensions, each reading ours + retail/ACE...",
    "[integration-loop] 7 claims → adversarial verify",
    "[collision-transition] 8 claims → adversarial verify",
    "[friction-smoothing] 8 claims → adversarial verify",
    "[jump-fall-statemachine] 9 claims → adversarial verify",
    "[dual-predictor] 8 claims → adversarial verify",
    "[motion-velocity-source] 9 claims → adversarial verify",
    "[server-reconciliation] 8 claims → adversarial verify",
    "Done. 57 claims across 7 dimensions. Verdicts: {\"confirmed\":44,\"partially-correct\":13}"
  ],
  "result": {
    "dimensions": [
      {
        "dimension": "integration-loop",
        "summary": "Retail/ACE integrate player physics on a fixed-quantum subdivision: each frame's delta is broken into <=0.1s (MaxQuantum) chunks with a 1/30s floor (MinQuantum), frames >2s (HugeQuantum) are skipped entirely, and within each quantum the position is advanced with a true second-order term `0.5*a*q^2 + v*q`, velocity hard-clamped at 50 m/s. Our authoritative Rust/wasm integrator (advance_local_pose_for_manual_drive, driven once per rAF via tickMovement) does NONE of this: it consumes the raw, unbounded per-frame Instant delta with no MaxQuantum subdivision, integrates gravity with first-order Euler (`vz -= 9.8*dt; z += vz*dt`) omitting the `0.5*a*t^2` half-step, and has no terminal-velocity clamp. The cli full-solve path (advance_body_kinematics) is likewise plain Euler `coords + v*dt`. The only dt safety net (Math.min(dt,0.1)) lives in the JS dead-reckon predictors (camera.js + 2D index.html), which are planar-only (no Z/gravity) and do not gate the authoritative integrator. Practical consequence: on a frame hitch the Rust integrator over-integrates the fall in one giant unclamped step (and the fall can exceed 50 m/s), whereas retail would either subdivide it into 0.1s slices or skip it past 2s — a documented source of position overshoot/desync.",
        "claims": [
          {
            "id": "no-maxquantum-subdivision",
            "statement": "Our wasm authoritative integrator consumes the full per-frame rAF delta (Instant::now() since last tick) with no MaxQuantum-style subdivision, whereas ACE/retail subdivide each frame into <=0.1s quanta in a while-loop.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/handle.rs",
              "lines": "96-106",
              "snippet": "let dt = match self.last_tick_at { Some(prev) => now.saturating_duration_since(prev), None => Duration::from_millis(16) }; ... self.inner.advance_local_pose_for_manual_drive(world, dt);  // raw dt, no subdivision"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "4175-4186",
              "snippet": "while (deltaTime > PhysicsGlobals.MaxQuantum) { PhysicsTimer_CurrentTime += PhysicsGlobals.MaxQuantum; UpdateObjectInternal(PhysicsGlobals.MaxQuantum); deltaTime -= PhysicsGlobals.MaxQuantum; } if (deltaTime > PhysicsGlobals.MinQuantum) { ... UpdateObjectInternal(deltaTime); }"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "integration-loop",
              "verdict": "confirmed",
              "reasoning": "Both cited locations are real and current. OURS handle.rs:96-100 computes `dt = now.saturating_duration_since(prev)` (fallback 16ms only on the first tick), where `now` is the live per-frame timestamp — the wasm caller passes `Instant::now()` (commands.rs:1977), so `dt` is the raw inter-frame rAF delta with no upper bound. That `dt` is fed straight into `advance_local_pose_for_manual_drive` (system.rs:597-721), where `dt_s = dt.as_secs_f32()` (line 626) is used in a SINGLE integration step: `raw_delta = velocity * dt_s` (lines 717-721) and gravity `vertical_velocity -= 9.8 * dt_s; coords.z += vertical_velocity * dt_s` (lines 937-939). I searched the entire function and the tick path for any quantum subdivision while-loop or temporal dt-clamp and found none — the only `while` loops and `clamp` calls are SPATIAL collision clamps (against walls/buildings/entities), not time subdivision. ACE PhysicsObj.cs:4175-4180 is exactly as quoted: `while (deltaTime > PhysicsGlobals.MaxQuantum) { ...; UpdateObjectInternal(MaxQuantum); deltaTime -= MaxQuantum; }`, with `MaxQuantum = 0.1f` confirmed at PhysicsGlobals.cs:41 (the 0.2f variant is commented out as buggy). So our integrator consumes the full per-frame delta unsubdivided while ACE subdivides into <=0.1s quanta. The kind=divergence/high-confidence classification is accurate.",
              "corrections": "No errors in the claim. Two additions that strengthen (not weaken) it, both missed by the finder: (1) ACE's update_object has more temporal guards than just MaxQuantum subdivision — a TickRate gate `if (deltaTime < TickRate) return false` with TickRate=1/30 (PhysicsObj.cs:4140,4163), a HugeQuantum=2.0f drop-the-frame guard (PhysicsObj.cs:4169-4173, PhysicsGlobals.cs:43), and a MinQuantum=1/30 lower-bound gate on the trailing remainder (PhysicsObj.cs:4182, PhysicsGlobals.cs:38). Our path has none of these. (2) Our handle.rs:96 `dt` has no upper clamp at all, so a long rAF stall (tab refocus, GC pause) produces one giant integration step — exactly the case ACE's HugeQuantum guard exists to drop. This makes the divergence broader than 'just no subdivision'.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/handle.rs:96-122; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/system.rs:597-721,937-939; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/commands.rs:1977; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:4140-4190; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/PhysicsGlobals.cs:38-43"
            }
          },
          {
            "id": "first-order-euler-gravity",
            "statement": "Our gravity step uses first-order Euler (z += vz*dt after vz -= 9.8*dt) and omits the 0.5*a*t^2 position half-step that retail/ACE apply; retail integrates `0.5*a*quantum^2 + v*quantum`.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/system.rs",
              "lines": "936-942",
              "snippet": "if world.player.is_airborne { let dt_s = dt.as_secs_f32(); world.player.vertical_velocity -= 9.8 * dt_s; pose.coords.z += world.player.vertical_velocity * dt_s; }"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "1854-1858",
              "snippet": "var movement = Acceleration * 0.5f * quantum * quantum + Velocity * quantum; frameOffset.Origin += movement; ... Velocity += Acceleration * quantum;"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "integration-loop-gravity-half-step",
              "verdict": "confirmed",
              "reasoning": "I independently opened both cited locations and they match the finder's evidence exactly, current today. OURS system.rs:935-942: airborne branch does `world.player.vertical_velocity -= 9.8 * dt_s;` then `pose.coords.z += world.player.vertical_velocity * dt_s;` — a single per-tick first-order step with NO `0.5*a*dt^2` position half-step. I grepped the whole movement system: the only gravity math is these two lines (plus the g=9.8 comment at 931-934); there is no half-step term, clamp, or substep/subdivision loop anywhere that would close the gap (line 537's max_step is a lateral-motion limiter, not a vertical substep). ACE PhysicsObj.cs:1854 reads `var movement = Acceleration * 0.5f * quantum * quantum + Velocity * quantum;` then 1858 `Velocity += Acceleration * quantum;` — exactly the closed-form Δx = v₀·t + ½·a·t² the finder quoted. I also cross-checked the retail decomp acclient.c:317756-317769 (CPhysicsObj::UpdatePhysicsInternal), which computes the same per-axis half-step (e.g. w.z = vz·q + 0.5·az·q²) before updating velocity at 317774. So the divergence is real: retail/ACE use the exact-integral kinematic step with the ½·a·t² term and update position with the OLD velocity; ours omits the half-step and uses symplectic (velocity-first) Euler. The kind=divergence / high-confidence classification is correct.",
              "corrections": "Two minor refinements that do not change the verdict: (1) Our step is more precisely symplectic/semi-implicit Euler, not plain explicit Euler — velocity is updated FIRST (line 938) and position then uses the NEW velocity (line 939). Retail/ACE instead update position with the OLD velocity plus the half-step, then update velocity. So in addition to the missing ½·a·t² term, the velocity-update ordering also differs. (2) The finder's phrasing \"z += vz*dt after vz -= 9.8*dt\" is accurate, but note our `vz -= 9.8*dt` already bakes gravity into vz before the position step, so the per-tick error vs. the exact integral is bounded and small (the code comment at 932-934 claims <1cm over a ~1s jump, which is roughly the right order of magnitude). The divergence is genuine but the practical magnitude is modest at 16ms ticks.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/system.rs:935-942; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1854-1858; /home/wbterminal/ac-headers/acclient.c:317756-317776"
            }
          },
          {
            "id": "retail-gravity-via-acceleration",
            "statement": "In retail, gravity is applied as m_accelerationVector.z = PhysicsGlobals.gravity (-9.8) by calc_acceleration when the GRAVITY state flag is set, so it flows through the second-order 0.5*a*q^2 position term; ours applies 9.8 directly to velocity (acceleration vector never carries gravity), so the half-step term is structurally absent.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/system.rs",
              "lines": "938-939",
              "snippet": "world.player.vertical_velocity -= 9.8 * dt_s; pose.coords.z += world.player.vertical_velocity * dt_s;  // no acceleration vector; 9.8 baked into velocity step"
            },
            "retail": {
              "file": "ac-headers/acclient.c",
              "lines": "317814-317819",
              "snippet": "if ( BYTE1(v4) & 4 ) { v6 = PhysicsGlobals::gravity; *(_DWORD *)v5 = 0; *((_DWORD *)v5 + 1) = 0; *((float *)v5 + 2) = v6; }  // m_accelerationVector.z = gravity"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "integration-loop-gravity",
              "verdict": "confirmed",
              "reasoning": "Independently verified every element. OURS system.rs:938-939 reads exactly `world.player.vertical_velocity -= 9.8 * dt_s;` then `pose.coords.z += world.player.vertical_velocity * dt_s;` — first-order semi-implicit (symplectic) Euler: gravity applied directly to velocity, position then uses the NEW velocity, with no 0.5*a*dt^2 term. The only quadratic/pow in the file (line 677) is friction smoothing, unrelated to gravity. There is no acceleration vector anywhere in our Z integration. RETAIL acclient.c:317814-317819: CPhysicsObj::calc_acceleration sets m_accelerationVector.z = PhysicsGlobals::gravity when `BYTE1(state) & 4`, which is bit 0x400 = GRAVITY_PS (confirmed acclient.h:2827 GRAVITY_PS=0x400; acclient.txt value 1024). PhysicsGlobals::gravity = -9.8000002 (acclient.c:45824). That acceleration vector then flows into the position update in CPhysicsObj::UpdatePhysicsInternal (acclient.c:317756-317769): `w.z = a.z*0.5` (317758), position term `w.z = q*v.z + 0.5*a.z*q*q` (317764+317766), `m_fOrigin.z += w.z` (317769) — i.e. the explicit second-order half-step term — and velocity is updated AFTER position (317771-317776). ACE matches retail bit-for-bit: PhysicsObj.cs:2079-2080 sets Acceleration=(0,0,Gravity) under PhysicsState.Gravity, and PhysicsObj.cs:1854 computes `Acceleration*0.5f*quantum*quantum + Velocity*quantum`. So in both retail and ACE gravity flows through the 0.5*a*q^2 second-order position term; ours applies 9.8 directly to velocity with the half-step term structurally absent. The divergence/high-confidence classification is correct and not overstated.",
              "corrections": "None material. Two minor clarifications that strengthen rather than alter the claim: (1) Our codebase ALREADY documents this gap — common.rs:360-364 states \"The wasm-side integrator skips the explicit-acceleration step\" and common.rs:386-398 explicitly describes retail calc_acceleration producing (0,0,gravity) airborne, so this is a known/acknowledged divergence, not a hidden one. (2) The divergence is more precisely also an integration-scheme difference: retail/ACE use an explicit second-order kinematic (x = x0 + v0*q + 0.5*a*q^2) with OLD velocity, while ours uses semi-implicit/symplectic Euler (velocity updated first, then position uses the new velocity). The finder's phrasing \"the half-step term is structurally absent\" captures this correctly. The constant: ours uses `-= 9.8` (= -9.8) vs retail/ACE -9.8000002 — effectively identical, no discrepancy.",
              "checked": "crates/holtburger-core/src/client/movement/system.rs:935-942, crates/holtburger-core/src/client/movement/common.rs:360-405, ac-headers/acclient.c:317756-317828, ac-headers/acclient.c:45824, ac-headers/acclient.h:2827, external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:2073-2084 and 1832-1859"
            }
          },
          {
            "id": "no-terminal-velocity-clamp",
            "statement": "Our integrators (wasm advance_local_pose_for_manual_drive and cli advance_body_kinematics) have no terminal-velocity clamp; retail/ACE hard-clamp velocity magnitude to MaxVelocity = 50 m/s every quantum.",
            "kind": "gap",
            "ours": {
              "file": "crates/holtburger-world/src/spatial/physics.rs",
              "lines": "1116-1118",
              "snippet": "let mut next_pose = input.pose; next_pose.rotation = Quaternion::from_heading(next_heading); next_pose.coords = next_pose.coords + (next_velocity * dt_secs);  // no MaxVelocity clamp anywhere"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "1843-1847",
              "snippet": "if (velocity_mag2 > PhysicsGlobals.MaxVelocitySquared) { Velocity = Vector3.Normalize(Velocity) * PhysicsGlobals.MaxVelocity; velocity_mag2 = PhysicsGlobals.MaxVelocitySquared; }  // MaxVelocity = 50.0f"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "integration-loop-terminal-velocity-clamp",
              "verdict": "confirmed",
              "reasoning": "Independently re-opened all cited code. ACE PhysicsObj.cs:1843-1847 hard-clamps velocity magnitude every quantum: `if (velocity_mag2 > PhysicsGlobals.MaxVelocitySquared) { Velocity = Vector3.Normalize(Velocity) * PhysicsGlobals.MaxVelocity; ... }`. PhysicsGlobals.cs:30,32 confirm MaxVelocity = 50.0f and MaxVelocitySquared = MaxVelocity*MaxVelocity, so the 50 m/s constant is real and current. On our side, advance_body_kinematics (holtburger-world physics.rs:1098-1128) computes next_velocity then does `next_pose.coords = next_pose.coords + (next_velocity * dt_secs)` with zero magnitude clamp. The wasm advance_local_pose_for_manual_drive (holtburger-core movement/system.rs:597 through ~1025) has friction decay, a per-axis accel cap, a small-velocity (0.25 m/s) snap, lateral collision clamps, and gravity integration (`vertical_velocity -= 9.8 * dt_s`), but NO terminal-velocity magnitude clamp — vertical velocity accumulates unbounded on a long fall. A broad grep across both the movement and spatial modules found no MaxVelocity/max_velocity/terminal/speed_cap constant; every `50.0` hit is a test coordinate. The gap classification is accurate and not overstated.",
              "corrections": "Minor path/scope nuance: the finder's OURS evidence line (physics.rs:1116-1118) pins only advance_body_kinematics; it does not point at the wasm function it also names. advance_local_pose_for_manual_drive actually lives in crates/holtburger-core/src/client/movement/system.rs:597, not in holtburger-world physics.rs. The finder's wasm/cli labels and the conclusion are still correct — I verified the wasm function separately and it likewise lacks the clamp. Also worth noting (does not change the verdict): ACE's clamp guards a behavioral path (small-velocity zeroing, calc_friction, ballistic Acceleration*0.5*q^2 term) that our integrators also do not fully replicate, so MaxVelocity is one of several integration-loop divergences, but as a standalone claim it is accurate.",
              "checked": "OURS: crates/holtburger-world/src/spatial/physics.rs:1098-1128 and crates/holtburger-core/src/client/movement/system.rs:597-1025; ACE: external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1832-1860 and PhysicsGlobals.cs:30-36"
            }
          },
          {
            "id": "no-hugequantum-skip",
            "statement": "Retail/ACE skip integration entirely when a frame's deltaTime exceeds HugeQuantum (2.0s), merely consuming the time; ours has no such guard, so a >2s frame hitch in the wasm integrator over-integrates one huge unclamped step (worst with the missing terminal clamp on a fall).",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/handle.rs",
              "lines": "96-100",
              "snippet": "let dt = match self.last_tick_at { Some(prev) => now.saturating_duration_since(prev), None => Duration::from_millis(16) }; self.last_tick_at = Some(now);  // no HugeQuantum skip; dt can be arbitrarily large"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "4169-4173",
              "snippet": "if (deltaTime > PhysicsGlobals.HugeQuantum) { UpdateTime = PhysicsTimer.CurrentTime; return false; }  // HugeQuantum = 2.0f"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "integration-loop-hugequantum-guard",
              "verdict": "confirmed",
              "reasoning": "I independently opened both cited locations and the surrounding context. ACE side: PhysicsObj.cs:4169-4173 is exactly as described — `if (deltaTime > PhysicsGlobals.HugeQuantum) { UpdateTime = PhysicsTimer.CurrentTime; return false; }`, which skips integration and merely consumes the elapsed time. The constant is real and current: PhysicsGlobals.cs:43 `public const float HugeQuantum = 2.0f;` (and MaxQuantum = 0.1f line 41, MinQuantum = 1/30 line 38). The same guard is mirrored in update_animation (4209-4213). OURS side: handle.rs:96-100 computes dt as `now.saturating_duration_since(prev)` (else 16ms) with NO upper clamp; the dt then flows unclamped into advance_local_pose_for_manual_drive (called at handle.rs:106). The upstream caller runtime.rs:171-180 passes only `now` (an Instant) and applies no dt cap before the movement tick. Inside the integrator (system.rs:716-721, 935-943) dt_s = dt.as_secs_f32() is used directly for both lateral (velocity * dt_s) and airborne integration. A single >2s frame therefore over-integrates one unclamped Euler step. The fall sub-claim is also verified: system.rs:938-939 do `vertical_velocity -= 9.8 * dt_s; pose.coords.z += vertical_velocity * dt_s` with no terminal-velocity clamp anywhere (grep for clamp/terminal/min/max on vertical_velocity returns nothing), so the worst case is exactly as the finder states. The divergence classification (kind=divergence, high confidence) is correct and arguably understated.",
              "corrections": "Two strengthening details the finder omitted (neither changes the conclusion): (1) ACE doesn't merely guard against >2s frames — it also subdivides every frame above MaxQuantum=0.1s into 0.1s sub-steps via the while loop at PhysicsObj.cs:4175-4180, so ACE NEVER integrates a single large step even below HugeQuantum, whereas ours always integrates exactly one step at the full dt. This makes the divergence broader than just the 2s threshold. (2) The cited source is ACE's server-side update_object (authoritative physics), not the retail acclient.c client integrator; the claim says 'Retail/ACE' and cites ACE only, which is accurate, but strictly the retail-client equivalent was not cross-checked in acclient.c.",
              "checked": "OURS: crates/holtburger-core/src/client/movement/handle.rs:96-106; crates/holtburger-core/src/client/movement/system.rs:597-626,716-721,935-943; crates/holtburger-core/src/client/runtime.rs:171-180 | ACE: external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:4159-4190; external/ACE/Source/ACE.Server/Physics/PhysicsGlobals.cs:38-43"
            }
          },
          {
            "id": "js-dt-cap-predictor-only",
            "statement": "The only dt clamp on our side (Math.min(dt, 0.1)) lives in the JS dead-reckon predictors (3D camera.js and 2D index.html) and is planar-only (X/Y from WASD, no Z/gravity); it does NOT gate the authoritative Rust integrator, which receives raw Instant deltas via tickMovement.",
            "kind": "risk",
            "ours": {
              "file": "apps/holtburger-web/scene3d/camera.js",
              "lines": "999-1003",
              "snippet": "// Cap dt at 100 ms ... matches 2D path's Math.min((now - lastPredictionTime) / 1000, 0.1)\n    const dtSafe = Math.min(dt, 0.1); if (!(dtSafe > 0)) return;  // only X/Y advanced below (lines 1060-1070); no Z"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsGlobals.cs",
              "lines": "38-43",
              "snippet": "MinQuantum = 1.0f/30.0f; MaxQuantum = 0.1f; HugeQuantum = 2.0f;  // retail's 0.1 cap is a physics quantum applied via subdivision, not a planar-only predictor clamp"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "integration-loop",
              "verdict": "confirmed",
              "reasoning": "Every load-bearing assertion checks out against the source as it exists today. (1) The Math.min(dt,0.1) clamp is real and current at camera.js:1002 (within cited 999-1003) and at index.html:10013 (the 2D path the camera.js comment references). (2) Both JS clamps gate PLANAR-only prediction: the 2D block (index.html:10049-10075) advances only sprite.x/sprite.y and hard-sets predVz=0.0 at line 10008 (jump/gravity is explicitly server-authoritative per the comment); the 3D path advances only X/Y from WASD. So 'planar-only (X/Y from WASD, no Z/gravity)' is accurate. (3) The authoritative Rust integrator is NOT gated by this clamp: tickMovement (lib.rs:24158-24168) enqueues a raw web_time::Instant::now(); MovementSystemHandle::tick (handle.rs:96-100) computes dt = now.saturating_duration_since(prev) with NO .min(0.1) and a 16ms first-tick fallback, then passes it straight to advance_local_pose_for_manual_drive; system.rs:626 does dt_s = dt.as_secs_f32() used unclamped for both lateral motion (lines 717-720) and the gravity/Z arc (line 939: pose.coords.z += vertical_velocity * dt_s). (4) An exhaustive grep for quantum/MaxQuantum/MinQuantum/0.0333/dt.min across the movement module and the whole core crate found NO dt-magnitude clamp — the only 'quantum' hits are comments citing PhatSDK friction. (5) ACE's MaxQuantum = 0.1f is at PhysicsGlobals.cs:41 (within cited 38-43), and ACE has the full MinQuantum/MaxQuantum/HugeQuantum trio that our integrator lacks. The 'risk' classification (high) is justified: the server-side reference architecture per-tick-clamps its physics quantum while our authoritative client integrator feeds raw inter-tick deltas.",
              "corrections": "Minor refinement, not a contradiction: handle.rs:97 uses saturating_duration_since, which prevents negative/backward-time deltas (e.g. monotonic-clock edge cases) — but this is a sign guard, not a magnitude clamp, so it does not bound a large positive delta from a resumed/throttled tab and does not weaken the claim. Also worth noting the finder cited camera.js as the only-clamp site but the equivalent live clamp is co-located in index.html:10013 (the 2D path), which the claim text does acknowledge.",
              "checked": "camera.js:999-1003,1008; index.html:10008-10075; src/lib.rs:24158-24168; crates/holtburger-core/src/client/movement/handle.rs:90-123; crates/holtburger-core/src/client/movement/system.rs:626,717-721,937-941; external/ACE/Source/ACE.Server/Physics/PhysicsGlobals.cs:38-43"
            }
          },
          {
            "id": "tickmovement-driven-by-raf",
            "statement": "tickMovement is invoked once per requestAnimationFrame from index.html with no fixed-timestep accumulator, so the authoritative integrator's quantum equals the variable rAF frame interval rather than retail's bounded [1/30s, 0.1s] quantum.",
            "kind": "divergence",
            "ours": {
              "file": "apps/holtburger-web/index.html",
              "lines": "9969",
              "snippet": "try { handle.tickMovement(); } catch (_) {}  // one call per rAF; tick_movement enqueues TickMovement{ now: Instant::now() } (lib.rs:24158-24168)"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "4140-4164",
              "snippet": "public static float TickRate = 1.0f / 30.0f; ... var deltaTime = PhysicsTimer.CurrentTime - UpdateTime; if (deltaTime < TickRate) return false;  // fixed TickRate gate + quantum subdivision below"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "integration-loop",
              "verdict": "confirmed",
              "reasoning": "Both cited locations are real and current. OURS: index.html:9969 `handle.tickMovement()` sits inside `drainEvents` (defined at 8608, self-reschedules via `requestAnimationFrame(drainEvents)` at 10232/10234), so it is invoked exactly once per rAF frame. `tickMovement` (lib.rs:24158-24168) merely enqueues `SessionCommand::TickMovement{ now: Instant::now() }`; the recv loop (lib.rs:33169, calls `movement.tick(now,...)` at 33519) then runs handle.rs::tick, which computes `dt = now.saturating_duration_since(last_tick_at)` (handle.rs:96-100) with NO clamp and a first-call-only 16ms fallback, then calls `advance_local_pose_for_manual_drive(world, dt)`. There the position delta is `smoothed_planar * dt_s` (system.rs:717-721, dt_s = dt.as_secs_f32()) and friction is `(1-F).powf(dt_s)` — straight raw-dt integration with no fixed-timestep accumulator and no MaxQuantum-style subdivision. I searched the whole index.html and found no accumulator/fixedStep/FIXED_STEP. RETAIL/ACE: PhysicsObj.cs:4140-4190 `update_object` is the authoritative integrator; PhysicsGlobals.cs:38-43 gives MinQuantum=1/30f, MaxQuantum=0.1f, HugeQuantum=2.0f. The retail loop skips integration when deltaTime<TickRate(1/30), bails when deltaTime>HugeQuantum, and otherwise subdivides accumulated time into MaxQuantum(0.1s) chunks via the `while` loop (4175-4180) plus a final sub-MaxQuantum step gated at MinQuantum (4182-4186). So each UpdateObjectInternal step is bounded to ≤0.1s, matching the finder's \"[1/30s, 0.1s]\" characterization. The divergence (variable rAF dt vs bounded retail quantum) is real and correctly classified as high-severity for the wasm authoritative path.",
              "corrections": "Minor, non-conclusion-changing: (1) `tickMovement` does not itself integrate — it enqueues a command; the actual dt computation/integration is in crates/.../movement/handle.rs:96-99 and system.rs:717-721. The line ref the finder gave (index.html:9969) is the per-frame *invocation* site, which is accurate, but the real \"no-accumulator, raw-dt\" integrator lives in the Rust crate, not in index.html. (2) There IS a dt clamp `Math.min(dt, 0.1)` at index.html:10013, but it belongs to the *separate* 2D-sprite JS prediction shadow (a parallel predictor), not to the wasm authoritative integrator the claim targets — so it does not rescue the cited path and the claim stands. (3) Retail also has a HugeQuantum=2.0s upper guard that drops the whole step (returns false) rather than integrating; the bounded-quantum range is [MinQuantum,MaxQuantum] per integration step, which the finder's \"[1/30s,0.1s]\" correctly captures.",
              "checked": "apps/holtburger-web/index.html:9969,10232; apps/holtburger-web/src/lib.rs:24158,33519; crates/holtburger-core/src/client/movement/handle.rs:96; crates/holtburger-core/src/client/movement/system.rs:717; external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:4163; external/ACE/Source/ACE.Server/Physics/PhysicsGlobals.cs:38"
            }
          }
        ],
        "open_questions": [
          "Does ACE's server-authoritative reconciliation (UpdatePosition broadcasts) correct the client's over-integrated/unclamped position frequently enough that the missing MaxQuantum/HugeQuantum guards never produce a user-visible desync in practice? The client integrator only feeds the AutonomousPosition heartbeat — the magnitude of real-world overshoot depends on how aggressively ACE snaps it back, which I could not quantify from source alone.",
          "Is the airborne path the only place gravity is integrated in the wasm bundle, or does the cli full-solve path (advance_grounded_body_kinematics / solve_self_player_local_drive at physics.rs:1149-1153) apply any vertical acceleration with a half-step? I read advance_body_kinematics (pure Euler, no Z accel) but did not fully trace the grounded variant's vertical handling.",
          "Retail's UpdateObjectInternal subdivides BOTH translation and turning (Omega * quantum via GRotate) per quantum; our single-step integrator applies omega*dt once per frame. For fast camera turns during a large frame, does the un-subdivided rotation produce a measurably different heading than retail's stepped GRotate, or is the difference negligible at typical 60Hz cadence? Not resolvable without a numeric A/B.",
          "The PLAYER_GROUND_FRICTION_PER_SEC = 0.5 vs retail DefaultFriction = 0.95 difference (system.rs:677 vs PhysicsGlobals.cs:15) is acknowledged in-code as an approximation, but since friction is applied per-quantum in retail (pow(1-f, quantum) summed over subdivided quanta) and per-frame in ours (pow(1-f, dt) once), the effective decay over a hitched frame diverges nonlinearly — magnitude unquantified."
        ]
      },
      {
        "dimension": "collision-transition",
        "summary": "Our collision solver is a flat, single-pass geometric clamp-and-slide that is structurally and behaviorally far simpler than retail/ACE's CTransition+SpherePath+BSP machinery. Confirmed: (a) ours uses swept-sphere-vs-inflated-AABB (buildings) + a one-Z-sample swept-circle-vs-triangle (cells) + 2D cylinder-vs-cylinder (entities), with NO BSP tree, while retail walks a per-cell PhysicsBSP polygon tree via CTransition::find_collisions with up to 2 spheres (NumSphere<=2) forming a cylsphere. (b) Ours has literally NO step_up/step_down/edge_slide/cliff_slide code — those four mechanics (which give retail stair-climbing, ledge edge-sliding, and cliff-sliding) are entirely absent; the only related token is an AllowEdgeSlide property bool hydrated for wire fidelity but never consumed. (c) Ours uses a cell-AABB rubberband net indoors that is both the safety fallback AND the sole indoor clamp before per-poly triangles bake. The walkable-slope threshold also diverges: ours treats normal.z>=0.5 (60deg) as floor vs retail's FloorZ=0.66417 (48.4deg), and there is a separate stand-alone JS dead-reckon predictor (camera.js _advancePrediction) that does ZERO collision clamping and can briefly walk visually through walls until server reconcile. Our slide is a single-iteration velocity-component-removal projection vs retail's iterative contact-plane cross-product skid with CalcNumSteps substepping. Net gameplay-visible result: no stair climbing (player blocked by any riser taller than nothing, since there is no step-up at all), no smooth edge-sliding along cliff tops, coarser slope walkability, and indoor pop-to-AABB-floor on ramped cells.",
        "claims": [
          {
            "id": "no-step-up-down",
            "statement": "Our codebase contains NO step-up or step-down climbing logic anywhere in the world/movement crates, whereas retail/ACE implements Transition.StepUp/StepDown driven by per-object StepUpHeight/StepDownHeight (default 0.039999999m, or Setup.dat values), so retail can auto-climb stair risers and curbs that our solver treats as impassable vertical walls.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-world/src/spatial/physics.rs",
              "lines": "336-529",
              "snippet": "clamp_delta_against_cell_walls(...) — for each wall triangle (|normal.z|<=WALL_NORMAL_MAX 0.7) clamp earliest_t and slide; there is no Z step-up. (grep of holtburger-world + movement/ for step_up/step_down returns only the AllowEdgeSlide property-bool hydration, never a solver use.)"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Transition.cs",
              "lines": "746-777",
              "snippet": "public bool StepUp(Vector3 collisionNormal){ ... var stepDownHeight = 0.039999999f; ... if (ObjectInfo.State.HasFlag(OnWalkable)){ stepDownHeight = ObjectInfo.StepUpHeight; } var stepDown = StepDown(stepDownHeight, zLandingValue); ... }"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "collision-transition",
              "verdict": "confirmed",
              "reasoning": "The core divergence is real and current. ACE: Transition.cs:746-777 defines StepUp(collisionNormal) which sets SpherePath.StepUp=true, uses stepDownHeight=0.039999999f as a fallback, overrides it with ObjectInfo.StepUpHeight when OnWalkable, and calls StepDown(stepDownHeight, zLandingValue) (StepDown body at Transition.cs:710-729). ObjectInfo.cs:30-31,46-47 populates StepUpHeight/StepDownHeight from PhysicsObj.GetStepUpHeight/GetStepDownHeight -> PartArray.cs:236-248 (Setup._dat.StepUpHeight * Scale.Z, else PhysicsGlobals.DefaultStepHeight). StepUp/StepDown are woven through TransitionalInsert (Transition.cs:298,449-479,828-879). So retail genuinely auto-climbs risers/curbs up to the per-object step height. OURS: I independently confirmed (grep across crates) that step_up/step_down appear ONLY as parsed Setup.dat fields (setup_model.rs:310-311,405-439,508-509,562-563) and as zeroed event-bake struct defaults (sky_chain.rs/anim_sound.rs) — with ZERO consumers of .step_up/.step_down outside the DAT parser, and NO occurrences at all in holtburger-world or holtburger-core (the movement/physics crates). The only 'climb' hit is an unrelated fall-damage test. Vertical resolution in physics.rs is purely highest_floor_z_under (floor raycast, normal.z>=0.5) plus clamp_delta_against_cell_walls (treats |normal.z|<=0.7 as an impassable wall and slides laterally, lines 397, 513-528). A vertical riser/curb has normal.z~=0, so it is clamped as a wall with no step-up — exactly the impassable-wall behavior the finder describes. Line refs (physics.rs:336-529, Transition.cs:746-777) and the parsed-but-unused Setup.dat step fields all check out. Classification as a high-confidence divergence is correct.",
              "corrections": "Minor constant imprecision, not affecting the conclusion: the finder phrases the default as 'default 0.039999999m, or Setup.dat values.' The literal 0.039999999f does appear in ACE, but only as the hardcoded fallback inside StepUp()/CheckWalkable when the object is NOT OnWalkable (Transition.cs:754, 850). The actual engine-wide default is PhysicsGlobals.DefaultStepHeight = 0.01f (PhysicsGlobals.cs:58), used when Setup is null, and the per-object value is Setup._dat.StepUpHeight/StepDownHeight * Scale.Z (PartArray.cs:240,247). So there are effectively three sources (0.01f global default, the 0.039999999f non-walkable fallback, and Setup.dat per-object values) rather than the single 0.039999999m default the finder implies. Also note our setup_model.rs DOES parse step_up/step_down from Setup.dat — the data is read but never fed into any movement solver — so 'our codebase contains NO step-up logic' is true specifically for the movement/collision path, while the underlying DAT values are in fact available and ignored.",
              "checked": "crates/holtburger-world/src/spatial/physics.rs:274-529; crates/holtburger-dat/src/file_type/setup_model.rs:296-320,405-439; external/ACE/Source/ACE.Server/Physics/Transition.cs:710-777; external/ACE/Source/ACE.Server/Physics/ObjectInfo.cs:30-47; external/ACE/Source/ACE.Server/Physics/PartArray.cs:236-248; external/ACE/Source/ACE.Server/Physics/PhysicsGlobals.cs:58"
            }
          },
          {
            "id": "no-edge-cliff-slide",
            "statement": "Retail/ACE implements EdgeSlide, CliffSlide, and PrecipiceSlide to keep a walking actor sliding along the lip of a ledge/cliff instead of walking off, gated by ObjectInfoState.EdgeSlide (in the default PhysicsState). Our solver has none of these; walking off a ledge just transitions to a ballistic fall once the terrain-Z delta exceeds a 0.5m threshold, with no edge-slide containment.",
            "kind": "gap",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/system.rs",
              "lines": "1012-1019",
              "snippet": "const LEDGE_FALL_THRESHOLD_M: f32 = 0.5; if pose.coords.z - z > LEDGE_FALL_THRESHOLD_M { world.player.begin_fall(); } else { pose.coords.z = z; } — pure fall vs snap; no edge-slide."
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Transition.cs",
              "lines": "242-320",
              "snippet": "public bool EdgeSlide(...) { ... transitionState = SetEdgeSlide(true,false, CliffSlide(CollisionInfo.ContactPlane)); ... } public TransitionState CliffSlide(Plane contactPlane){ var contactNormal = Vector3.Cross(contactPlane.Normal, LastKnownContactPlane.Normal); ... }"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "collision-transition",
              "verdict": "confirmed",
              "reasoning": "Every load-bearing element checks out in the source as it stands today. ACE side: Transition.cs:242-266 is CliffSlide and 268-320 is EdgeSlide (with SetEdgeSlide at 322-334), exactly as cited; PrecipiceSlide lives at SpherePath.cs:232-255 and is called from inside EdgeSlide (Transition.cs:284, 309). The gate is real: EdgeSlide (Transition.cs:270) returns early unless ObjectInfoState.OnWalkable AND ObjectInfoState.EdgeSlide are both set; ObjectInfoState.EdgeSlide = 0x200 (ObjectInfo.cs:19) is stamped from PhysicsState.EdgeSlide (PhysicsObj.cs:2589-2590), and PhysicsState.EdgeSlide IS part of the default PhysicsState (PhysicsGlobals.cs:25-26, DefaultState). EdgeSlide is invoked from the walk-off/StepDown path at Transition.cs:886. The retail decomp independently corroborates all three: CTransition::cliff_slide (acclient.c:312005), CTransition::edge_slide (312685), SPHEREPATH::precipice_slide (313980), plus ObjectInfoState EDGE_SLIDE=0x200 and PhysicsState EDGE_SLIDE_PS=0x400000 — so 'retail/ACE' is accurate, not just ACE. OURS side: system.rs:1012-1019 is verbatim — const LEDGE_FALL_THRESHOLD_M: f32 = 0.5; then `if pose.coords.z - z > LEDGE_FALL_THRESHOLD_M { world.player.begin_fall(); } else { pose.coords.z = z; }`. begin_fall (types.rs:751) just flips is_airborne and zeroes vertical velocity — pure ballistic transition, no lateral containment. I confirmed by grep that the entire holtburger movement solver never references edge-slide, cliff-slide, precipice, walkable-edge crossing, or SlideSphere; the only EdgeSlide references in our crates are pure wire-data plumbing (object.rs PhysicsState bitflag, bools.rs PropertyBool::AllowEdgeSlide, hydration.rs copying the bool, and a protocol test fixture) — none feed the collision solver. The gap/kind classification is correct and if anything understated: we don't merely lack the slide, we parse and hydrate the AllowEdgeSlide flag and then ignore it entirely in motion.",
              "corrections": "Two minor refinements that do not change the verdict: (1) The claim says 'gated by ObjectInfoState.EdgeSlide' — accurate but incomplete; the early-return gate at Transition.cs:270 requires BOTH ObjectInfoState.OnWalkable AND ObjectInfoState.EdgeSlide, and EdgeSlide itself is the dispatcher between CliffSlide/PrecipiceSlide rather than a leaf behavior. (2) Our codebase is not blind to the concept — it DOES parse PhysicsState::EDGE_SLIDE (0x00400000) and hydrate PropertyBool::AllowEdgeSlide (object.rs:78, hydration.rs:286-287), it simply never consults that flag in the movement solver, which arguably strengthens the gap finding.",
              "checked": "crates/holtburger-core/src/client/movement/system.rs:1012-1019; crates/holtburger-world/src/player/types.rs:751-762; crates/holtburger-world/src/hydration.rs:285-288; external/ACE/Source/ACE.Server/Physics/Transition.cs:242-320,886; external/ACE/Source/ACE.Server/Physics/SpherePath.cs:232-255; external/ACE/Source/ACE.Server/Physics/PhysicsGlobals.cs:25-26; external/ACE/Source/ACE.Server/Physics/ObjectInfo.cs:19; external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:2589-2590; /home/wbterminal/ac-headers/acclient.c:312005,312685,313980"
            }
          },
          {
            "id": "no-bsp-flat-triangle-scan",
            "statement": "Retail/ACE collides the moving sphere(s) against a per-cell PhysicsBSP polygon tree (CellStruct.PhysicsBSP.find_collisions / BSPTREE/BSPNODE/BSPLEAF::hits_walkable), while our indoor solver does a flat linear scan over a triangle bag with an AABB pre-cull and a single mid-height capsule sample (clamp_delta_against_cell_walls), explicitly NOT a BSP and not a full swept-capsule-vs-triangle test.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-world/src/spatial/physics.rs",
              "lines": "388-460",
              "snippet": "let mid_z = global.z + height * 0.5; ... for tri in triangles { ... } — single Z sample at mid-height, linear scan, no BSP. Comment: 'samples the capsule axis at one Z height rather than integrating along the segment'."
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Common/EnvCell.cs",
              "lines": "92-99",
              "snippet": "if (CellStructure.PhysicsBSP != null) { ... transitState = CellStructure.PhysicsBSP.find_collisions(transition, 1.0f); }"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "collision-transition",
              "verdict": "confirmed",
              "reasoning": "Both sides verified by reading the cited files directly. OURS (physics.rs:336-529): clamp_delta_against_cell_walls(_with_exclusions) takes a flat `&[Triangle]` slice and does a single `for tri in triangles` linear scan (line 393). It has an AABB pre-cull (tri Z-range vs capsule, lines 401-403; plus the WALL_NORMAL_MAX=0.7 plane-orientation skip at 379/397). It samples the capsule at ONE mid-height Z: `mid_z = global.z + height*0.5` (line 386), used for start/end points (430-431) — not a swept capsule along the segment. The function's own doc-comment (lines 324-331) states verbatim it is \"a coarser proxy than a full swept-capsule-vs-triangle test (it samples the capsule axis at one Z height...)\". The caller (holtburger-core movement/system.rs:794) passes a flat triangle slice. No BSP exists on this indoor cell-wall path (grep of spatial/ finds BSP only in entity_collision.rs, an unrelated stubbed entity path). RETAIL/ACE (EnvCell.cs:83-104): FindEnvCollisions dispatches to CellStructure.PhysicsBSP.find_collisions / placement_insert (lines 96-99). CellStruct.PhysicsBSP is a real BSPTree field (CellStruct.cs:20). BSPTree.find_collisions (BSPTree.cs:131) recurses through RootNode, a BSPNode with PosNode/NegNode children (BSPNode.cs:35-36) holding Polygons/InPolys, with hits_walkable / sphere_intersects_poly / sphere_intersects_solid — i.e. swept-sphere(s)-vs-polygon BSP tree. The decomp confirms BSPTREE/BSPNODE/BSPLEAF::find_collisions and ::hits_walkable as real symbols. Line refs, constants (WALL_NORMAL_MAX 0.7, mid-height 0.5), and code forms are all real and current. The divergence classification is accurate, not overstated.",
              "corrections": "Minor scope nuance the finder did not mention (does not change the conclusion): OURS does contain a `HAS_PHYSICS_BSP` flag and a stated intent for per-polygon BSP collision, but only in spatial/entity_collision.rs and only for ENTITY GfxObj collision — and even there it \"currently falls through to cylinder\" (entity_collision.rs:27-29), so it is not wired. It is a separate path from the indoor cell-wall solver the claim targets, so the claim's \"our indoor solver ... explicitly NOT a BSP\" remains correct for clamp_delta_against_cell_walls. Also worth noting the finder's cited OURS range 388-460 is slightly short: the full flat-scan body runs 393-511 and the load-bearing doc-comment admitting the single-Z-sample non-swept proxy is at 313-335 — both strengthen the claim.",
              "checked": "OURS: /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/spatial/physics.rs:313-529 (and caller system.rs:794); ACE: /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Common/EnvCell.cs:83-104, BSP/BSPTree.cs:131-168, BSP/BSPNode.cs:35-168, Common/CellStruct.cs:20-46; DECOMP: /home/wbterminal/ac-headers/acclient.c (BSPTREE/BSPNODE/BSPLEAF::find_collisions, ::hits_walkable symbols)"
            }
          },
          {
            "id": "walkable-slope-threshold",
            "statement": "The walkable-slope cosine threshold diverges: our floor classifier accepts any triangle with plane normal.z >= 0.5 (slopes up to 60deg) as floor, but retail/ACE treats a surface as walkable only when normal.z >= FloorZ = 0.66417414618662751 (acclient.c CPhysicsObj::is_valid_walkable returns normal->z >= PhysicsGlobals::floor_z), i.e. slopes up to ~48.4deg. Our solver therefore lets players stand/walk on steeper inclines than retail allows.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-world/src/spatial/physics.rs",
              "lines": "283-292",
              "snippet": "const FLOOR_NORMAL_MIN: f32 = 0.5; // '60deg slope' ... if plane.normal.z < FLOOR_NORMAL_MIN { continue; }"
            },
            "retail": {
              "file": "/home/wbterminal/ac-headers/acclient.c",
              "lines": "316500-316510",
              "snippet": "BOOL CPhysicsObj::is_valid_walkable(Vector3 *normal){ return normal->z >= (double)PhysicsGlobals::floor_z; } // floor_z = 0.66417414618662751 (ACE PhysicsGlobals.FloorZ)"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "collision-transition",
              "verdict": "confirmed",
              "checked": "physics.rs:283,292; system.rs:1047-1058; acclient.c:316500-316503,800530; PhysicsGlobals.cs:50; PhysicsObj.cs:2861-2864",
              "reasoning": "Verified. physics.rs:283 sets FLOOR_NORMAL_MIN=0.5, line 292 rejects normal.z below it; used by movement solver at system.rs:1048 (indoor floor snap). No 0.66417/floor_z in crates/*.rs. acclient.c:316502 is_valid_walkable returns normal.z>=floor_z; acclient.c:800530 floor_z=cos(3437.746770784939)=0.66417, matching ACE PhysicsGlobals.cs:50 FloorZ=0.66417414618662751f and PhysicsObj.cs:2863. acos(0.5)=60deg, acos(0.66417)=48.38deg. We allow steeper inclines than retail. Divergence real.",
              "corrections": "Minor: our highest_floor_z_under is a narrower indoor/EnvCell per-polygon floor-Z snap helper, not a 1:1 analog of retail is_valid_walkable; wording is scoped to the indoor path. Threshold divergence itself is genuine."
            }
          },
          {
            "id": "single-iter-slide-vs-iterative",
            "statement": "Our sliding is a single-iteration velocity-component-removal (remaining - normal*(remaining.dot(normal))) applied once for buildings and once for cell walls, with no substepping. Retail/ACE substeps the whole transition (CalcNumSteps = ceil(dist/sphereRadius) steps) and slides via a contact-plane cross-product skid (SlideSphere uses Cross(collisionNormal, contactPlane.Normal)), iterating insert→collide→slide per step, so retail handles concave corners and fast motion that our single pass can tunnel/jitter through.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-world/src/spatial/physics.rs",
              "lines": "154-173",
              "snippet": "let into_normal = remaining.dot(&hit.normal); let slide = remaining - hit.normal * into_normal; ... let slide_clamped = match sweep_sphere_against_aabbs(...) — exactly one re-sweep on the slide remainder, then done."
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Sphere.cs",
              "lines": "473-521",
              "snippet": "var direction = Vector3.Cross(collisionNormal, skid_dir); ... skid_dir = Vector3.Dot(globOffset, direction) * direction; skid_dir *= invDirLenSq; ... return TransitionState.Slid; — plus Transition.CalcNumSteps (Transition.cs:97-140) substeps the path by ceil(dist/radius)."
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "collision-transition",
              "verdict": "confirmed",
              "reasoning": "Every load-bearing element checks out against the source as it exists today. OURS: clamp_delta_against_buildings (physics.rs:142-173) computes the slide exactly as the finder states — `let slide = remaining - hit.normal * into_normal;` where `into_normal = remaining.dot(&hit.normal)` (lines 154-156), applied once. clamp_delta_against_cell_walls_with_exclusions (physics.rs:368-528) uses the identical form `let slide = remaining - normal * into_normal;` (lines 526-527), also once. Neither has a `ceil(dist/radius)` substep loop — grep for loop/while/for-step across the slide functions found none; the only iteration is over candidate AABBs/triangles to find the FIRST contact, not over path substeps. The integrator call site (movement/system.rs:790-848) applies cell-walls then optional cell-interior indoors, or buildings outdoors — once each per frame, on mutually-exclusive branches. The buildings path does a single re-clamp of the slide vector (lines 169-171) but that is still a single slide pass, not multi-step. ACE: CalcNumSteps (Transition.cs:97-140) sets `step = dist / SpherePath.LocalSphere[0].Radius` (line 108) and `numSteps = (int)Math.Ceiling(step)` (line 114) — exactly ceil(dist/sphereRadius). FindTransitionalPosition (Transition.cs:532-591) loops `for (var step = 0; step < numSteps; step++)`, each iteration adds offsetPerStep to CheckPos, runs TransitionalInsert(3) (the insert/collision test), and ValidateTransition — i.e. insert→collide→slide per step. SlideSphere (Sphere.cs:473-521) slides via `var direction = Vector3.Cross(collisionNormal, skid_dir);` where `skid_dir = contactPlane.Normal` (lines 486-489), the cross-product skid the finder describes. The divergence classification is correct and, if anything, understated — OURS is genuinely a single non-substepped pass, which can tunnel/jitter on fast motion and mishandle concave corners that ACE's per-step iteration resolves.",
              "corrections": "One addition the finder omitted (does not change the conclusion): the OURS indoor path also applies clamp_delta_to_cell_interior (physics.rs:205-255), which is NOT a velocity-component-removal slide at all — it's a `.clamp()` of global X/Y into the radius-inset cell AABB. So indoors the divergence is arguably even larger/cruder than \"single-iteration slide\" implies: a hard AABB box-clamp plus one wall-slide pass, versus ACE's per-step swept transition. The finder's `remaining - normal*(remaining.dot(normal))` form accurately describes the building and cell-wall slides specifically. Minor: ACE's CalcNumSteps has a separate IsViewer branch (Transition.cs:130-139) using floor(step)+1, but for the player/non-viewer case the finder's ceil(dist/radius) is exact.",
              "checked": "OURS: /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/spatial/physics.rs:142-173,205-255,336-528,1061-1082 and crates/holtburger-core/src/client/movement/system.rs:780-849 | ACE: /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Transition.cs:97-140,494-593 and Sphere.cs:465-521"
            }
          },
          {
            "id": "js-predictor-no-collision",
            "statement": "The separate JS dead-reckon predictor (camera.js _advancePrediction) advances predictedPlayerPos purely from WASD intent x heading x speed x dt with ZERO geometry collision clamping; it relies entirely on the wasm authoritative integrator + server reconcile to correct it. This means the visually-rendered local player can momentarily walk through walls/buildings until the next reconcile snaps it back, a behavior retail's fully-authoritative client never exhibits.",
            "kind": "risk",
            "ours": {
              "file": "apps/holtburger-web/scene3d/camera.js",
              "lines": "1050-1071",
              "snippet": "this.predictedPlayerPos.x += Math.sin(effHeading) * speed * dtSafe; this.predictedPlayerPos.y += Math.cos(effHeading) * speed * dtSafe; — no clamp_delta call; collision sweeps in this file are camera-only (positionCamera/sweepCameraTarget)."
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Transition.cs",
              "lines": "142-204",
              "snippet": "CheckCollisions/CheckOtherCells run the full FindCollisions transition before any pose is committed — the retail client never advances the local actor without running collision."
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "collision-transition",
              "verdict": "partially-correct",
              "reasoning": "The core mechanism is factually accurate and both line refs are real and current. OURS camera.js:1050-1071 (`_advancePrediction`) advances `predictedPlayerPos.x/y` purely as `sin/cos(effHeading) * speed * dtSafe` with ZERO geometry collision clamping — I read the full function (982-1099) and the reconcile path (`_reconcilePrediction` 851-923, `_applyPredictionLerp` 1118-1132): the only corrections are a 5m teleport snap and a 150ms lerp toward the server/wasm pose. No AABB/wall/raycast clamp exists anywhere on this path (grep of camera.js + entities.js setPose confirms only `clampSign` input-clamp and camera-pitch clamp). Crucially the claim is right that this drives the VISUALLY-RENDERED player, not just the camera: loop.js:257-290 sets the rig mesh via `entityManager.setPose(guid, predicted.x, predicted.y, renderZ, ...)` using the un-clamped predicted XY, and entities.js:6353-6356 makes the predicted pose the first-priority source for `getLocalPlayerWorldPos()`. The claim's premise that the wasm authoritative integrator DOES clamp (so a real client/render divergence exists) is verified: system.rs:722-820 applies outdoor building_aabb_index sweep-sphere clamps and indoor per-poly wall + cell-AABB clamps. The ACE/retail ref is correct: Transition.cs:142-204 (`CheckCollisions`/`CheckOtherCells`) is the real transition engine returning Collided/Slid/Adjusted, and it ports retail acclient's CTransition (confirmed CTransition.obj, find_building_collisions@CBuildingObj, cliff_slide@CTransition in acclient.txt) which the fully-authoritative retail client runs locally so the rendered player never penetrates walls. The kind=risk classification is correct. I mark partially-correct only because the SEVERITY phrasing 'walk through walls/buildings' overstates magnitude: the reconcile is a continuous 150ms lerp toward the continuously-updated wasm-clamped pose (not a discrete delayed snap), so when a player runs into a wall the predicted XY settles to a bounded sub-meter overshoot past the wall plane, not free traversal through whole structures. It is a real, visible transient clipping artifact retail never exhibits — but a small standoff/overshoot, not 'walking through buildings.'",
              "corrections": "Severity is overstated: the artifact is a bounded sub-meter transient overshoot/clipping of the player rig past a wall plane, NOT walking cleanly through walls/buildings. Reason: reconcile is a continuous 150ms inverse-time lerp (_lerpDurationMs=150.0, camera.js:378) toward the wasm-clamped pose which is re-targeted on every 30Hz server-ts change (camera.js:918-921), so JS forward drift (~4.5 m/s) is continuously opposed by the lerp toward the stationary wall pose — steady-state overshoot is well under a meter, not building-traversal. Also note the claim says reconcile 'snaps it back', but only the >5m delta path is an instant snap (camera.js:904-911); the normal sub-5m correction is a 150ms lerp, so the visual is a brief overshoot-and-glide-back, not a hard snap. Minor ref note: the finder cited camera.js:953-954 inside loop.js for the X/Y-only advance; the actual advance lines are camera.js:1060-1061/1068-1069 (953-954 is a doc-comment line range), but this is a stale cross-reference in OURS' own comment, not the finder's error.",
              "checked": "apps/holtburger-web/scene3d/camera.js:1050-1071,851-923,1118-1132,378; apps/holtburger-web/scene3d/loop.js:236-291; apps/holtburger-web/scene3d/entities.js:6338-6356; crates/holtburger-core/src/client/movement/system.rs:722-820; external/ACE/Source/ACE.Server/Physics/Transition.cs:142-204; /home/wbterminal/ac-headers/acclient.txt:726,1181,3231"
            }
          },
          {
            "id": "indoor-floor-pop-aabb-fallback",
            "statement": "Indoors, when per-poly triangles are not yet baked, our solver clamps lateral motion to the cell's world-space AABB (clamp_delta_to_cell_interior) and snaps Z to a single floor value (highest_floor_z_under, else cell_aabb.min.z + 0.005m). On ramped/multi-level cells before triangles load this pops the player to the cell's lowest floor Z rather than following the ramp, and the AABB box-clamp cannot represent non-rectangular cells until triangles arrive — a coarse proxy for retail's exact per-polygon walkable/contact-plane resolution.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/system.rs",
              "lines": "1047-1061",
              "snippet": "let floor_z = if !triangles.is_empty() { highest_floor_z_under(...).or_else(|| cell_aabb.map(|a| a.min.z)) } else { cell_aabb.map(|a| a.min.z) }; ... let snap_z = floor + 0.005; if pose.coords.z < snap_z { pose.coords.z = snap_z; }"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Sphere.cs",
              "lines": "403-414",
              "snippet": "if (!transition.SpherePath.IsWalkableAllowable(disp.Z)) return OK; ... var contactPlane = new Plane(disp, -Vector3.Dot(disp, disp2)); transition.CollisionInfo.SetContactPlane(contactPlane, true); — exact per-poly contact plane, not an AABB box."
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "collision-transition",
              "verdict": "confirmed",
              "reasoning": "I independently re-read both sides. OURS (system.rs:1047-1061): when an indoor cell's triangles are empty, floor_z = cell_aabb.map(|a| a.min.z) (line 1056), then snap_z = floor + 0.005 (line 1059) — exactly the \"cell_aabb.min.z + 0.005m single floor value\" the claim states. When triangles ARE present it uses highest_floor_z_under (line 1048). The lateral side (system.rs:793-835 + physics.rs:205-255 clamp_delta_to_cell_interior) confirms: with triangles empty but AABB present, pre_clamped = lateral and the motion is box-clamped to the world-space cell AABB (inset by capsule radius). The claim's \"pops the player to the cell's lowest floor Z rather than following the ramp\" is corroborated verbatim by the code's own comment at system.rs:956-958 (\"for a ramped floor the player visually pops to the cell's lowest point\") and physics.rs:191-198 (\"coarser proxy than per-polygon collision... doesn't model interior obstacles\"). The RETAIL/ACE counterpart (Sphere.cs:403-414) is genuine per-polygon resolution: IsWalkableAllowable(disp.Z) gates walkability (def at SpherePath.cs:227-230) and a per-contact Plane is constructed (line 408) and set as the contact plane — exactly the \"exact per-polygon walkable/contact-plane resolution\" the claim contrasts against. All constants, function names, and both cited line ranges are exact and current. The divergence/coarse-proxy classification is correct and, if anything, understated given the code's own self-description.",
              "corrections": "One imprecision (does not change the verdict): the claim's umbrella phrasing \"when per-poly triangles are not yet baked\" actually spans two distinct unbaked states. (1) When NEITHER AABB nor triangles exist (indoor_unbaked = true at system.rs:746-752), the solver does the OPPOSITE of the claim: lateral motion is frozen to Vector3::zero() (line 791) and the Z floor-snap is skipped entirely (lines 1022-1026), not snapped to min.z. (2) The behavior the claim describes (AABB box-clamp + snap to cell_aabb.min.z + 0.005) only occurs in the narrower AABB-present-but-triangles-absent window. That window is real, reachable, and explicitly documented (system.rs:1031-1034: \"Fall back to cell_aabb.min.z when they aren't (initial seconds after landblock entry, before the lazy physics bake completes)\"), and AABBs vs triangles drain from separate pending piles (lib.rs:9177 drain_pending_cell_graph_into for AABBs vs lib.rs:9203 drain_pending_cell_physics_into for triangles), so they can land in different frames. The claim is internally consistent with the AABB-present state since it presupposes an AABB exists (\"the AABB box-clamp cannot represent non-rectangular cells until triangles arrive\"). No constant, line ref, or classification is wrong.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/system.rs:1047-1061, :746-836, :944-958; crates/holtburger-world/src/spatial/physics.rs:205-255; apps/holtburger-web/src/lib.rs:9176-9212; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Sphere.cs:403-414; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/SpherePath.cs:227-230"
            }
          },
          {
            "id": "single-sphere-vs-cylsphere",
            "statement": "Retail/ACE models the player as a cylinder-of-spheres with up to 2 spheres (SpherePath.NumSphere<=2, low + high sphere) so collision is checked at both feet and head height; our building sweep uses ONE sphere at chest height and our cell-wall sweep samples ONE mid-height circle, so half-height obstacles (railings, low ledges) and head-only overhangs are modeled less faithfully.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-world/src/spatial/physics.rs",
              "lines": "19-20",
              "snippet": "pub const PLAYER_CAPSULE_RADIUS: f32 = 0.4; pub const PLAYER_CAPSULE_HEIGHT: f32 = 1.8; — building sweep uses sphere-at-chest-height (radius 0.4); cell-wall sweep samples mid_z = z + height*0.5."
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/SpherePath.cs",
              "lines": "106-118",
              "snippet": "public void InitSphere(int numSphere, List<Sphere> spheres, float scale){ if (numSphere <= 2) NumSphere = numSphere; else NumSphere = 2; ... } — two spheres define the collision cylinder; IntersectsSphere tests both GlobalSphere[0] and GlobalSphere[1]."
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "collision-transition",
              "verdict": "confirmed",
              "reasoning": "Both cited locations are real and current. OURS physics.rs:19-20 defines PLAYER_CAPSULE_RADIUS=0.4 / HEIGHT=1.8 (single capsule), and the surrounding code confirms a single-sphere model: the building sweep is explicitly \"sphere-at-chest-height\" (physics.rs:48-51, 1057) sweeping one inflated AABB ray from one Z; the cell-wall sweep samples ONE mid-height circle at mid_z = global.z + height*0.5 (physics.rs:386, 430-431). There is no second/high sphere anywhere in crates/holtburger-world/src/spatial/. ACE SpherePath.cs:106-118 InitSphere clamps NumSphere<=2 exactly as the finder quotes, sets LocalLowPoint from LocalSphere[0] (the low sphere), and the two distinct local-frame spheres (index 0 low/feet, index 1 high/head) are stacked vertically and BOTH tested independently throughout collision: CylSphere.cs:233-301 (globSphere_ = GlobalSphere[1]), BSPTree.cs:139/149/197-205, Sphere.cs:296-376. The retail decomp confirms ACE is a 1:1 port: acclient.c:313662-313665 `if (_num_sphere <= 2) this->num_sphere = _num_sphere; else this->num_sphere = 2;`. So retail/ACE genuinely checks collision at two heights (feet+head) while OURS uses one; half-height railings and head-only overhangs are modeled less faithfully. OURS even self-documents the exact gap at physics.rs:329-331 (\"upgrade to full capsule-segment-vs-triangle if a wall-cap interaction surfaces (e.g. a half-height railing)\"). The divergence classification is correct.",
              "corrections": "Two minor refinements, neither weakens the claim. (1) ACE/retail clamp NumSphere to at most 2, but a given Setup can have fewer (e.g. NumSphere<2 branches exist in Transition.cs:220/464/861 and Sphere.cs:624); the actual player human Setup count comes from Setup.NumSphere/PartArray.GetNumSphere(), and the code is written to handle 0/1/2 — the \"up to 2\" phrasing is accurate but the player isn't guaranteed to be 2 by this code alone (it's data-driven). (2) The finder says OURS \"folds vertical extent into the AABBs\" mitigates this for buildings; in reality inflate() (physics.rs:64) expands the AABB uniformly by radius and the ray still starts at chest height, so a head-only overhang whose AABB sits entirely above chest+radius would still be missed — this reinforces, not weakens, the divergence. Classification could arguably be slightly understated rather than overstated.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/spatial/physics.rs:19-20,48-76,336-529,1061-1082; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/SpherePath.cs:106-120; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/CylSphere.cs:233-301; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/BSP/BSPTree.cs:139-205; /home/wbterminal/ac-headers/acclient.c:313662-313665"
            }
          }
        ],
        "open_questions": [
          "Exact human Setup (0x02000001) StepUpHeight/StepDownHeight values are not directly readable in the provided sources — GetStepUpHeight reads Setup._dat.StepUpHeight*Scale.Z (ACE PartArray.cs:243-248) and the default fallback when Setup==null is DefaultStepHeight=0.01m (PhysicsGlobals.cs:58); retail Transition.StepUp uses 0.039999999 as the un-walkable default. The precise per-human DAT value (commonly cited ~0.1m) would need a DAT dump to confirm, so the magnitude of the stair-climbing gap is bounded but not pinned.",
          "Our WALL_NORMAL_MAX=0.7 (wall if |normal.z|<=0.7, i.e. >=45.6deg from horizontal) and FLOOR_NORMAL_MIN=0.5 (floor if normal.z>=0.5) leave a 0.5..0.7 band where a triangle is treated as floor by the Z-snap raycast but NOT clamped as a wall by the lateral pass; whether this produces a real seam (a 48.4..60deg ramp the player can walk up but that no wall-clamps) on actual baked Holtburg/academy cells is unverified without a live run.",
          "Whether the wasm authoritative integrator's single-iteration slide ever tunnels through thin walls at high speed (run 4.5 m/s @ tick dt) was not measured; CalcNumSteps in retail substeps by ceil(dist/radius) so a 4.5 m/s run over a ~16ms tick = ~0.075m < radius 0.4m would be 1 step in retail too, suggesting tunneling risk is low at normal speeds but unconfirmed for teleport/knockback deltas.",
          "The entity-collision pass (clamp_delta_against_entities) is 2D cylinder-vs-cylinder ignoring Z entirely (entity_collision.rs:44-60 comments 'Z is ignored'); retail's CylSphere collision is height-aware. Whether any AC content (e.g. stepping over a small prone creature, or a tall creature's head) exposes this Z-collapse was not evaluated.",
          "The has_physics_bsp flag is plumbed on EntityCollider but the BSP-polygon branch is an unimplemented TODO (entity_collision.rs:102-104); the set of world objects that actually carry HAS_PHYSICS_BSP (and thus get only the cylinder fallback instead of true poly collision) was not enumerated."
        ]
      },
      {
        "dimension": "friction-smoothing",
        "summary": "Ours and retail share the exact friction FORM — a per-tick multiplicative decay v *= pow(1 - friction, quantum) gated on being grounded — and the 0.25 small-velocity snap matches retail SmallVelocity exactly. But the two systems diverge substantially in everything else: (1) ours uses friction coefficient 0.5 vs retail's 0.95 (a deliberate ~5x weaker damping, documented as compensation for ours skipping retail's explicit acceleration integration step); (2) ours adds an 8 m/s^2 lateral accel cap that retail has NO equivalent for (retail smooths purely via friction + Acceleration*quantum); (3) ours SKIPS retail's contact-plane normal-component removal entirely, doing scalar X/Y axis decay against a flat horizontal plane rather than projecting velocity onto the actual ground normal; and (4) ours omits the Sledding branches and the velocity-magnitude/normal-Z slope special cases. Net feel: ours decelerates noticeably slower on key-release (gentle 0.5/s decay over ~0.5s) and ramps up slower from rest (accel-cap limited), so stops and starts are softer than retail's snappier friction-driven settle. Crucially this entire friction model lives ONLY in the Rust/wasm integrator; the JS dead-reckon predictor in camera.js has no friction at all — it is a constant-velocity extrapolation that stops instantly on key-release and relies on server-pose lerp reconciliation.",
        "claims": [
          {
            "id": "friction-form-match",
            "statement": "Both ours and retail apply grounded friction as a per-tick multiplicative velocity decay v *= pow(1 - friction, quantum), gated on the grounded/OnWalkable transient state.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/system.rs",
              "lines": "677-679",
              "snippet": "let scale = (1.0 - PLAYER_GROUND_FRICTION_PER_SEC).powf(dt_s); v.x *= scale; v.y *= scale;"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "2122-2140",
              "snippet": "if (!TransientState.HasFlag(TransientStateFlags.OnWalkable)) return; ... var scalar = (float)Math.Pow(1.0f - friction, quantum); Velocity *= scalar;"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "friction-smoothing",
              "verdict": "partially-correct",
              "reasoning": "The core mechanism the claim asserts is real and current in both sources. OURS system.rs:677-679 computes `let scale = (1.0 - PLAYER_GROUND_FRICTION_PER_SEC).powf(dt_s); v.x *= scale; v.y *= scale;` (PLAYER_GROUND_FRICTION_PER_SEC=0.5, common.rs:374), inside the grounded `else` branch of `if world.player.is_airborne` (system.rs:664/671) — i.e. a per-tick multiplicative decay v *= pow(1-friction, quantum) gated on grounded state. ACE PhysicsObj.cs:2120-2141 (calc_friction) early-returns unless TransientState has OnWalkable (line 2122), then at 2139-2140 does `var scalar = (float)Math.Pow(1.0f - friction, quantum); Velocity *= scalar;` — the identical multiplicative-pow form gated on OnWalkable. Line refs are accurate (OURS exact; ACE 2122 gate + 2139-2140 decay sit inside the cited 2122-2140 range). The grounded≈OnWalkable gate equivalence is fair and is explicitly called out in OURS's own comments (657-663). So the claimed mechanism/form genuinely matches. I mark partially-correct rather than confirmed only because a literal `match` slightly overstates the parity: the friction CONSTANT differs (OURS hardcodes 0.5; ACE uses the per-object `Friction` field, retail-default ~0.95, which OURS's own comment at 648-649 acknowledges), and ACE's calc_friction carries terms OURS lacks — contact-plane normal projection (2124-2127), an `angle >= 0.25f` early-out (2125), and Sledding special-cases (2130-2137). None of these contradict the narrow claim, but they mean the full friction routine is not a clean 1:1 port. The decay form + grounded gate assertion itself is accurate.",
              "corrections": "The claim's mechanism is correct, but the `kind=match` classification is slightly overstated and should note divergences the finder did not surface: (1) friction constant differs — OURS hardcodes 0.5, ACE uses per-object Friction (~0.95 default), so steady-state decay rates differ even though the pow-form is shared; (2) ACE's calc_friction contains terms absent in OURS: contact-plane velocity projection `Velocity -= ContactPlane.Normal*angle` and the `if(angle>=0.25f) return` early-out (PhysicsObj.cs:2124-2127), plus Sledding-state friction overrides (2132-2136). OURS is a planar X/Y approximation with no contact plane. Also note OURS layers a non-retail per-axis acceleration cap (PLAYER_LATERAL_ACCELERATION_CAP=8.0, system.rs:684-692) and a velocity snap (695-708) on top of the decay; retail uses friction-only smoothing. So it is a faithful match on the decay form + grounded gate, but a partial/approximate match on the overall friction model.",
              "checked": "crates/holtburger-core/src/client/movement/system.rs:664-711; crates/holtburger-core/src/client/movement/common.rs:374,382,405; external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1832-1860,2120-2141"
            }
          },
          {
            "id": "friction-coeff-0.5-vs-0.95",
            "statement": "Ours uses friction coefficient 0.5 (PLAYER_GROUND_FRICTION_PER_SEC) where retail uses 0.95 (DefaultFriction) — a deliberate ~5x weaker per-tick decay, documented as compensation for ours skipping retail's explicit per-tick acceleration integration step.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "374",
              "snippet": "pub(super) const PLAYER_GROUND_FRICTION_PER_SEC: f32 = 0.5; // vs retail's 0.95"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsGlobals.cs",
              "lines": "15",
              "snippet": "public const float DefaultFriction = 0.95f;"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "checked": "common.rs line 374, system.rs line 677, PhysicsGlobals.cs line 15, PhysicsObj.cs lines 2120-2141 and 1854-1858",
              "claim_id": "friction-smoothing",
              "verdict": "partially-correct",
              "reasoning": "Verified independently. OURS PLAYER_GROUND_FRICTION_PER_SEC equals 0.5 at common.rs:374. ACE DefaultFriction equals 0.95f at PhysicsGlobals.cs:15, assigned to PhysicsObj.Friction at PhysicsObj.cs:129. Identical role: both feed pow(1-friction, quantum) - ours at system.rs:677, ACE at PhysicsObj.cs:2139. Genuine apples-to-apples. The skip-explicit-acceleration justification is real: ACE integrates accel at PhysicsObj.cs:1854 and 1858; our comments at common.rs:355-364 and system.rs:641-651 document this. 0.5 is the weaker decay. Divergence classification correct. Downgraded only for magnitude imprecision.",
              "corrections": "The ~5x figure is loose. Coefficient ratio is 1.9x (0.95 over 0.5); actual per-tick velocity-lost ratio is ~4.2x (60Hz: 1.15 percent vs 4.87 percent; 30Hz: 2.28 percent vs 9.50 percent), not 5x. Minor: ACE DefaultFriction is overridable per-object at PhysicsObj.cs:3557-3558 with Sledding special-cases, but 0.95 applies to a normal grounded player."
            }
          },
          {
            "id": "accel-cap-not-in-retail",
            "statement": "Ours adds an 8 m/s^2 per-axis lateral acceleration cap to ramp velocity toward the input target; retail has NO lateral accel cap — retail integrates velocity via Velocity += Acceleration * quantum where grounded lateral Acceleration is (0,0,0) and the input target is set directly via apply_raw_movement, smoothing only through friction.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/system.rs",
              "lines": "684-692",
              "snippet": "let accel_step = PLAYER_LATERAL_ACCELERATION_CAP_M_PER_SEC_SQ * dt_s; ... let clamped = delta.clamp(-accel_step, accel_step); *cur += clamped;"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "1854-1858",
              "snippet": "var movement = Acceleration * 0.5f * quantum * quantum + Velocity * quantum; frameOffset.Origin += movement; Velocity += Acceleration * quantum;"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "friction-smoothing",
              "verdict": "confirmed",
              "reasoning": "Independently verified all elements across three sources. OURS (system.rs:684-692) applies a per-axis lateral accel cap: accel_step = PLAYER_LATERAL_ACCELERATION_CAP_M_PER_SEC_SQ(8.0, common.rs:405) * dt_s, clamping the per-axis delta toward the input-derived target_velocity (defined line 616 via local_velocity_for_state). This is gated to the grounded branch only (airborne passes target through, 664-670). Retail/ACE has no such cap: ACE PhysicsObj.cs:1858 integrates Velocity += Acceleration * quantum, and calc_acceleration (2073-2084) sets Acceleration = Vector3.Zero when grounded (Contact & OnWalkable), so the grounded lateral acceleration is (0,0,0) — confirmed identically in the retail decomp acclient.c:317802-317804 (all-zero accel vector when transient_state bits 1&2 set; airborne only adds gravity to Z, never X/Y). The input target is set directly via apply_raw_movement (MotionInterp.cs:506) → set_local_velocity → set_velocity which assigns Velocity = velocity (PhysicsObj.cs:3970), with no ramp. The only per-tick lateral smoothing in retail is friction: calc_friction (PhysicsObj.cs:2139-2140) does Velocity *= pow(1-friction, quantum), matching retail acclient.c:316152-316154. Cited line refs are accurate (OURS 684 is the accel_step line, cap loop 685-692; ACE 1854-1858 spans the movement integration and the Velocity+=Acceleration*quantum). OURS's own comment at 680-683 self-attests the cap is a non-retail game-feel addition. The kind=divergence / high-confidence classification is correct and not overstated.",
              "corrections": "No factual errors. Minor additions for completeness (not contradicting the claim): (1) The accel cap is gated to the grounded branch only — when airborne, OURS passes target_velocity through directly (system.rs:664-670), which actually matches retail's instant-velocity behavior in that case. (2) OURS also diverges in its friction coefficient (PLAYER_GROUND_FRICTION_PER_SEC=0.5 vs retail's per-surface Friction value, e.g. ACE calc_friction uses the object's Friction field) and treats the whole thing as an approximation rather than a port — but the specific lateral-accel-cap claim is precisely accurate. (3) The accel cap works in concert with the friction decay applied at system.rs:677-679 before the clamp, not as the sole smoother.",
              "checked": "crates/holtburger-core/src/client/movement/system.rs:664-711; crates/holtburger-core/src/client/movement/common.rs:374-405; external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1854-1858, 2073-2084, 2120-2141, 3758-3762, 3966-3987; external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs:506-522; ac-headers/acclient.c:317787-317828, 316152-316154"
            }
          },
          {
            "id": "small-velocity-snap-match",
            "statement": "The 0.25 m/s small-velocity snap-to-zero matches retail's SmallVelocity exactly in both value and intent (zero residual drift below threshold), though ours additionally gates on the target also being below threshold to avoid killing ramp-up from rest.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "382",
              "snippet": "pub(super) const PLAYER_VELOCITY_SNAP_THRESHOLD_M_PER_SEC: f32 = 0.25; // ... if mag_sq < threshold_sq && target_mag_sq < threshold_sq { v.x=0; v.y=0; }"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "1851-1852",
              "snippet": "if (velocity_mag2 - PhysicsGlobals.SmallVelocitySquared < PhysicsGlobals.EPSILON) Velocity = Vector3.Zero; // SmallVelocity = 0.25f"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "friction-smoothing",
              "verdict": "confirmed",
              "reasoning": "Independently verified all three legs of the claim. VALUE: 0.25 confirmed in OURS (common.rs:382 PLAYER_VELOCITY_SNAP_THRESHOLD_M_PER_SEC = 0.25), ACE (PhysicsGlobals.cs:34 SmallVelocity = 0.25f), and retail decomp (acclient.c:317750 `velocity_mag2 - 0.25 * 0.25 < 0.00019999999`). LINE REFS: ACE PhysicsObj.cs:1851-1852 is exactly `if (velocity_mag2 - PhysicsGlobals.SmallVelocitySquared < PhysicsGlobals.EPSILON) Velocity = Vector3.Zero;` — correct as cited. INTENT: retail/ACE zero the velocity vector below the threshold = kill residual drift, matching the finder's description. ADDITIONAL TARGET-GATE: confirmed verbatim at the actual application site system.rs:705 — `if mag_sq < threshold_sq && target_mag_sq < threshold_sq { v.x = 0.0; v.y = 0.0; }`. The extra `target_mag_sq < threshold_sq` conjunct is precisely the \"gates on the target also being below threshold to avoid killing ramp-up from rest\" divergence the finder identified; the in-code comment at system.rs:697-702 states the same rationale. The comparison forms are mathematically equivalent (both compare squared-magnitude against squared-threshold; OURS drops only ACE's negligible EPSILON=0.0002 slack). No clamp, subdivision, or fallback was missed that changes the conclusion. The match/divergence classification (exact value+intent match, plus one documented intentional extra gate) is accurate and not overstated.",
              "corrections": "Minor pointer imprecision (does not affect the verdict): the finder cited OURS at common.rs:382, which is only the constant DEFINITION. The snap logic and the load-bearing extra target-gate actually live in a different file, system.rs:693-708 (gate at line 705). The finder's prose correctly describes that gate but its file:line evidence points at the constant rather than the application site, so a reader following only the cited line would not see the divergence code itself.",
              "checked": "crates/holtburger-core/src/client/movement/system.rs:705 (snap gate); crates/holtburger-core/src/client/movement/common.rs:382 (constant); external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1851-1852 (ACE gate); external/ACE/Source/ACE.Server/Physics/PhysicsGlobals.cs:34 (SmallVelocity=0.25f); /home/wbterminal/ac-headers/acclient.c:317750 (retail decomp)"
            }
          },
          {
            "id": "contact-plane-projection-skipped",
            "statement": "Ours SKIPS retail's contact-plane normal-component removal: retail computes angle = dot(Velocity, ContactPlane.Normal), returns early if angle >= 0.25, then removes the normal component (Velocity -= Normal * angle) BEFORE damping, projecting onto the actual ground slope. Ours damps X and Y scalar axes against an implicit flat horizontal plane with no normal projection and no 0.25 angle gate.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/system.rs",
              "lines": "677-679",
              "snippet": "// only v.x and v.y scaled; no ContactPlane.Normal, no dot-product gate, no normal-component subtraction"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "2124-2127",
              "snippet": "var angle = Vector3.Dot(Velocity, ContactPlane.Normal); if (angle >= 0.25f) return; Velocity -= ContactPlane.Normal * angle;"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "friction-smoothing",
              "verdict": "confirmed",
              "reasoning": "I independently opened both files. ACE PhysicsObj.cs calc_friction (lines 2120-2141) reads exactly as the finder states: line 2124 `var angle = Vector3.Dot(Velocity, ContactPlane.Normal);`, line 2125 `if (angle >= 0.25f) return;`, line 2127 `Velocity -= ContactPlane.Normal * angle;` (normal-component removal), and only AFTER that does the damping happen at lines 2139-2140 (`scalar = pow(1-friction, quantum); Velocity *= scalar;`). So retail gates on the 0.25 dot-product, removes the contact-plane normal component, THEN damps the full 3D velocity vector. Ours (system.rs lines 674-679) takes `current_planar_velocity`, computes `scale = (1.0 - PLAYER_GROUND_FRICTION_PER_SEC).powf(dt_s)`, and multiplies only `v.x` and `v.y` by it — pure scalar per-axis damping of the two horizontal components, with the Z axis explicitly untouched (comment lines 653-655). There is no ContactPlane, no dot product, no 0.25 angle gate, and no normal-component subtraction. I grepped the entire movement module and the whole core crate: zero hits for ContactPlane/contact_plane/normal_component/contact_normal. The only 0.25 in our friction path is PLAYER_VELOCITY_SNAP_THRESHOLD_M_PER_SEC (a velocity-magnitude snap-to-zero, semantically unrelated to retail's angle dot-product early-out). Our own code comments (lines 642-649) explicitly admit \"We don't port the full retail pipeline.\" Line refs, constants, and code forms are all real and current. The divergence classification is correct and not overstated.",
              "corrections": "Minor framing nuance, not an error in the claim: retail's `Velocity -= Normal * angle` removes the velocity component along the contact-plane normal (preventing motion into the surface); calling this \"projecting onto the actual ground slope\" is a reasonable shorthand but the primary effect is removing the into-surface component. The 0.25f gate is an early-out for objects already moving away from / only weakly into the surface, not a slope test per se. Also worth noting the same Dot(Velocity, ContactPlane.Normal) pattern recurs at PhysicsObj.cs:2219 and :2692 — this reinforces (does not weaken) the divergence. None of this changes the conclusion: ours genuinely skips the gate and the normal projection entirely.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:2120-2141 and /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/system.rs:664-711"
            }
          },
          {
            "id": "sledding-branches-omitted",
            "statement": "Ours omits retail's Sledding-state friction branches entirely: retail overrides friction to 1.0 when velocity_mag2 < 1.5625 and to 0.2 when velocity_mag2 >= 6.25 with a near-flat slope (ContactPlane.Normal.Z > 0.99999536). Ours has a single fixed 0.5 coefficient with no state-dependent or speed-dependent override.",
            "kind": "gap",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "374",
              "snippet": "const PLAYER_GROUND_FRICTION_PER_SEC: f32 = 0.5; // single fixed value, no Sledding/slope branches"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "2130-2137",
              "snippet": "if (State.HasFlag(PhysicsState.Sledding)) { if (velocity_mag2 < 1.5625f) friction = 1.0f; else if (velocity_mag2 >= 6.25f && ContactPlane.Normal.Z > 0.99999536f) friction = 0.2f; }"
            },
            "confidence": "high",
            "impact": "low",
            "verdict": {
              "claim_id": "friction-smoothing",
              "verdict": "confirmed",
              "reasoning": "I independently opened both sources. ACE PhysicsObj.cs calc_friction (line 2120) contains the exact Sledding branch the finder quoted: line 2130 `if (State.HasFlag(PhysicsState.Sledding))`, line 2132-2133 `if (velocity_mag2 < 1.5625f) friction = 1.0f;`, line 2135-2136 `else if (velocity_mag2 >= 6.25f && ContactPlane.Normal.Z > 0.99999536f) friction = 0.2f;`. The finder's cited range 2130-2137 is precisely this block — constants, comparison operators, and the 0.99999536f near-flat-slope guard all match the current file. ACE has a real per-object `Friction` field (default PhysicsGlobals.DefaultFriction, PhysicsObj.cs:59/129) overridden in these Sledding sub-cases; `Sledding` is a live PhysicsState flag consumed in friction logic (also at lines 1682, 2659). On OURS, common.rs:374 defines `PLAYER_GROUND_FRICTION_PER_SEC = 0.5` and the sole friction application site (system.rs:664-711, the multiply at line 677 `(1.0 - PLAYER_GROUND_FRICTION_PER_SEC).powf(dt_s)`) uses that single fixed coefficient gated only by airborne vs grounded — there is NO Sledding branch and NO velocity_mag2-dependent override anywhere. The `SLEDDING` symbol exists in OURS only as a bitflag definition (object.rs:79, 0x00800000) and is never read by any friction code (grep across all of crates/ returns exactly that one definition line). So the gap is real, the constants and line refs are current and exact, and the gap classification is correct and not overstated.",
              "corrections": "Minor scope note, not a defect in the claim: OURS does not merely drop the Sledding branches from an otherwise-faithful port of calc_friction — its grounded-friction model is a different approximation entirely. It omits retail's surface-normal velocity projection (`Velocity -= ContactPlane.Normal * angle`) and the `angle >= 0.25` early-return, and adds an acceleration cap (PLAYER_LATERAL_ACCELERATION_CAP_M_PER_SEC_SQ = 8.0) that retail has no equivalent for. These are additional divergences in the same method, consistent with (not contradicting) the Sledding-omission claim. The claim as worded is fully accurate.",
              "checked": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:2120-2141; crates/holtburger-core/src/client/movement/common.rs:374; crates/holtburger-core/src/client/movement/system.rs:664-711; crates/holtburger-common/src/properties/object.rs:79"
            }
          },
          {
            "id": "js-predictor-no-friction",
            "statement": "The JS dead-reckon predictor (camera.js) has NO friction model at all — it is a constant-velocity extrapolation (pos += dir * speed * dt) that advances only while WASD is held and stops instantly on key-release, relying on server-pose lerp reconciliation; all friction smoothing lives exclusively in the Rust integrator.",
            "kind": "risk",
            "ours": {
              "file": "apps/holtburger-web/scene3d/camera.js",
              "lines": "1006-1014",
              "snippet": "const inputForward = (k.w?1:0)-(k.s?1:0); ... if (inputForward === 0 && inputStrafe === 0) { return; } // no advancement, no decay"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "1849",
              "snippet": "calc_friction(quantum, velocity_mag2); // retail integrator always friction-decays residual velocity each tick"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "friction-smoothing",
              "verdict": "confirmed",
              "reasoning": "I independently opened both cited files and verified every element. OURS camera.js:1006-1014 is exactly as claimed: `inputForward = (k.w?1:0)-(k.s?1:0)`, `inputStrafe = (k.d?1:0)-(k.a?1:0)`, and when both are zero the function `return`s with NO advancement (key-release stops instantly). The actual extrapolation at lines 1060-1069 is pure constant-velocity: `predictedPlayerPos.x += sin(effHeading)*speed*dtSafe` / `.y += cos(...)` with no velocity store, no decay coefficient, no friction term — speed is just a fixed RUN/WALK scalar (4.5 / 1.0). I grepped the entire camera.js: it contains NO friction/decay/velocity-integration code; the only smoothing is the reconcile LERP (`_reconcilePrediction` at 851 + 150ms position blend toward `getLocalPlayerPose()`), which is a position blend, not a friction model. The friction model genuinely lives in the Rust integrator: system.rs:664-711 implements the friction-decay + accel-cap (`scale = (1.0 - PLAYER_GROUND_FRICTION_PER_SEC).powf(dt_s)`, friction=0.5, with ON_WALKABLE/airborne gating mirroring retail). The ACE anchor PhysicsObj.cs:1849 (`calc_friction(quantum, velocity_mag2)`) and its definition at 2120 (`Velocity *= pow(1.0 - friction, quantum)`) are real and current. The match/risk classification is correct: the JS predictor has a fundamentally different (friction-less, key-gated, snap-on-release) motion model than the server/integrator friction-decay model, which is a legitimate divergence/risk that relies entirely on reconciliation to mask.",
              "corrections": "Two minor imprecisions that do NOT change the conclusion: (1) The JS lerps toward `getLocalPlayerPose()`, which is the wasm/Rust CLIENT integrator's authoritative pose (where the friction-decay actually runs), not directly the ACE network server pose. The finder's phrase \"server-pose lerp reconciliation\" is loose — the smoothed pose the JS blends toward is produced by the Rust client integrator (system.rs:664-711), which is itself reconciled against the ACE server. (2) The Rust integrator's own comments cite PhatSDK `PhysicsObj.cpp:521-561` (not the ACE C# `PhysicsObj.cs`) as its friction reference, and it deliberately uses a gentler coefficient (0.5 vs retail 0.95) plus a non-retail accel-cap — so the Rust friction is an approximation of retail, not a 1:1 port. The finder's cited ACE line 1849 is the call site of `calc_friction`, not the friction math itself (that is at line 2120); both are valid anchors for \"where retail/ACE friction lives.\"",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/apps/holtburger-web/scene3d/camera.js:1006-1071, :851-922; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/system.rs:664-711; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1849, :2120-2141"
            }
          },
          {
            "id": "airborne-gate-vs-onwalkable",
            "statement": "Ours gates friction on the inverse of world.player.is_airborne (friction applied only when grounded), which functionally mirrors retail's TransientStateFlags.OnWalkable gate (calc_friction returns early when not OnWalkable); airborne in both keeps velocity un-damped.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/system.rs",
              "lines": "664-671",
              "snippet": "let smoothed_planar = if world.player.is_airborne { /* pass target through, no friction */ } else { /* friction + accel cap */ }"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "2122",
              "snippet": "if (!TransientState.HasFlag(TransientStateFlags.OnWalkable)) return;"
            },
            "confidence": "high",
            "impact": "low",
            "verdict": {
              "claim_id": "friction-smoothing",
              "verdict": "confirmed",
              "reasoning": "Both cited locations are real and current. OURS system.rs:664 gates the friction-decay branch on `if world.player.is_airborne { pass target through } else { apply (1.0 - PLAYER_GROUND_FRICTION_PER_SEC).powf(dt_s) decay }` — line 677 is the only friction/damping in the function (verified via grep: no second damping path), so friction is applied iff `!is_airborne` (grounded). `is_airborne` is a genuine bool field on PlayerState (holtburger-world/src/player/types.rs:484), set true on jump/fall and false on touchdown (types.rs:766), so `!is_airborne` == grounded. ACE PhysicsObj.cs:2122 is exactly `if (!TransientState.HasFlag(TransientStateFlags.OnWalkable)) return;` — calc_friction returns early when not OnWalkable. calc_friction is the sole velocity-damping call (PhysicsObj.cs:1849 inside UpdatePhysicsInternal); when airborne it no-ops and Velocity is only integrated by gravity Acceleration (line 1858), so airborne velocity is un-damped in ACE — matching the airborne pass-through in OURS. The match classification is correct; the OURS code comments independently cite the same retail ON_WALKABLE_TS gate.",
              "corrections": "One nuance the finder understated (does not change the verdict): ACE's OnWalkable is not a pure airborne/grounded boolean — it is set true only when in Contact AND ContactPlane.Normal.Z >= FloorZ (PhysicsObj.cs:1234), i.e. on a non-steep surface; on a too-steep slope an object can be grounded-but-not-OnWalkable and thus skip friction. OURS' !is_airborne has no slope-steepness condition, so the parity is exact only for walkable/flat ground. The finder's own wording (\"functionally mirrors\") accommodates this, so it is at most a minor omission, not an error.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/system.rs:664-711; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:2120-2141,1832-1860,1232-1241; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/player/types.rs:484,766; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/common.rs:374"
            }
          }
        ],
        "open_questions": [
          "The Phase 10.3 doc comment claims applying f=0.95 directly would create a 25-35% steady-state speed deficit because ours skips retail's explicit acceleration step, but I could not independently verify whether retail's apply_raw_movement actually re-sets m_velocityVector to the full input target each tick BEFORE friction (which would make retail's effective steady-state speed = target despite friction=0.95). If retail re-sets velocity to target every tick, retail's friction only ever damps the single-tick residual and 0.95 would NOT cause a deficit — meaning ours' choice of 0.5 may be over-corrected. The apply_raw_movement / set_velocity path in acclient.c was not traced to confirm.",
          "Whether ours' omission of the contact-plane projection causes observable behavior divergence on sloped terrain (the only case where it matters) — on flat ground the projection is a near no-op since Velocity is already roughly perpendicular to a vertical normal; needs an in-world slope test to quantify.",
          "Retail's effective per-tick quantum is clamped to MinQuantum=1/30 (0.0333s) and MaxQuantum=0.1s in PhysicsGlobals; ours uses the raw rAF dt (capped at 0.1 in the JS path but the Rust grounded branch uses dt_s directly). I did not confirm whether the Rust integrator clamps quantum to retail's [1/30, 0.1] window, which would affect friction-scale parity at very high or very low frame rates.",
          "The accel-cap gates the snap (snap only fires when BOTH current and target velocity are below 0.25), so a player decelerating from full run does not benefit from the snap until near-stopped — whether this produces a longer visible 'drift tail' than retail (which snaps as soon as velocity_mag2 < 0.0625) was not measured."
        ]
      },
      {
        "dimension": "jump-fall-statemachine",
        "summary": "Our jump-height/velocity formula and fall-damage formula are bit-faithful 1:1 ports of ACE/retail: compute_jump_velocity_z reproduces MovementSystem.GetJumpHeight (burdenMod * (skill/(skill+1300)*22.2 + 0.05) * power, 0.35 floor) followed by InqJumpVelocity's vz = sqrt(h*19.6), and compute_fall_damage reproduces Player_Move.HandleFallingDamage's overspeed/ratio*87.293810 math exactly including the 11.25434 baseline and 4.5 leeway. JumpStaminaCost and GetBurdenMod also match. The state machine is sound but DIVERGES from retail's physics model in three ways: (1) our gravity integration is symplectic Euler (v then x) whereas ACE uses the kinematic x += a*0.5*t² + v*t form (sub-cm per tick, accumulates on long falls); (2) we have NO terminal-velocity clamp where ACE caps at MaxVelocity=50 m/s; (3) fall damage is computed nowhere in production — compute_fall_damage has zero callers and is documentation-only, so all fall damage is left to the authoritative server. The Jump motion clip (0x2500003B) DOES exist in retail player motion tables (refuting the old \"0x003B absent\" note), and our begin_jump/begin_fall/land + Falling(0x40000015)/Fallen(0x40000008) emission is an invented-but-reasonable client visual state machine layered on top of retail's single InterpretedState.ForwardCommand model. The JS dead-reckon predictor in camera.js is X/Y-only and never touches Z, so jump/fall arcs are exclusively the wasm integrator's responsibility.",
        "claims": [
          {
            "id": "jump-height-formula-bit-identical",
            "statement": "compute_jump_velocity_z reproduces ACE MovementSystem.GetJumpHeight exactly: burdenMod * (skill/(skill+1300)*22.2 + 0.05) * power, with a 0.35m floor, then vz = sqrt(height*19.6) matching WeenieObject.InqJumpVelocity.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-world/src/player/types.rs",
              "lines": "701-716",
              "snippet": "let height = burden_mod * (skill / (skill + 1300.0) * 22.2 + 0.05) * power;\nlet height = height.max(0.35); // ACE min clamp\n(height * 19.6).sqrt()"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Animation/MovementSystem.cs + Common/WeenieObject.cs",
              "lines": "12-17, 93-95",
              "snippet": "var result = EncumbranceSystem.GetBurdenMod(burden) * (jumpSkill / (jumpSkill + 1300.0f) * 22.2f + 0.05f) * power / scaling;\nif (result < 0.35f) result = 0.35f;  ...  velocity_z = (float)Math.Sqrt(height * 19.6);"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "jump-fall-statemachine",
              "verdict": "confirmed",
              "reasoning": "I independently opened both codebases and the formula matches exactly. OURS compute_jump_velocity_z (types.rs:713-715) computes height = burden_mod * (skill/(skill+1300.0)*22.2 + 0.05) * power, applies a .max(0.35) floor, then sqrt(height*19.6). ACE GetJumpHeight (MovementSystem.cs:12) computes GetBurdenMod(burden) * (jumpSkill/(jumpSkill+1300.0f)*22.2f + 0.05f) * power / scaling with `if (result < 0.35f) result = 0.35f` (lines 14-15) — identical constants (1300, 22.2, 0.05, 0.35) and identical structure. The burdenMod term OURS inlines (types.rs:703-711) reproduces ACE EncumbranceSystem.GetBurdenMod (EncumbranceSystem.cs:34-39) precisely: burden<1.0→1.0, burden<2.0→2.0-burden, else→0.0. ACE WeenieObject.InqJumpVelocity (WeenieObject.cs:93,95) calls GetJumpHeight(burden, jumpSkill, extent, 1.0f) then velocity_z = sqrt(height * 19.6) — exactly the vz = sqrt(height*19.6) step OURS uses. Both clamp power to [0,1] before computing. The only ACE term OURS omits is the trailing `/ scaling`, but the sole live caller (InqJumpVelocity via MotionInterp.cs:648) always passes scaling=1.0f, so dividing by 1.0 is a no-op and omitting it is correct. I confirmed the other GetJumpHeight caller (Player.cs:891) is dead code — it sits inside a /* */ comment block (lines 883-894). The match classification with high confidence is correct.",
              "corrections": "No material errors. Two minor notes that do not change the verdict: (1) The finder did not mention ACE's `/ scaling` divisor in GetJumpHeight; OURS hardcodes the scaling=1.0 case, which is correct because every live caller passes 1.0f, but the finder's claim that the formula matches \"exactly\" is true only modulo this always-1.0 term. (2) The finder cites WeenieObject.cs:12-17 for the formula, but those lines are namespace/class/log-field declarations — the actual InqJumpVelocity math lives at WeenieObject.cs:73-98 (GetJumpHeight call at 93, sqrt at 95), which the finder's \"93-95\" citation correctly covers; the 12-17 cite is just header context.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/player/types.rs:701-716; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Animation/MovementSystem.cs:8-18; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Common/WeenieObject.cs:73-98; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Common/EncumbranceSystem.cs:32-40"
            }
          },
          {
            "id": "burdenmod-1to1",
            "statement": "Our burden_mod branch (1.0 for burden<1.0, 2.0-burden for 1.0..2.0, 0.0 for >=2.0) is a 1:1 port of ACE EncumbranceSystem.GetBurdenMod.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-world/src/player/types.rs",
              "lines": "703-711",
              "snippet": "let burden_mod = if burden < 1.0 { 1.0 } else if burden < 2.0 { 2.0 - burden } else { 0.0 };"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Common/EncumbranceSystem.cs",
              "lines": "32-40",
              "snippet": "if (burden < 1.0f) return 1.0f; if (burden < 2.0f) return 2.0f - burden; else return 0.0f;"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "jump-fall-statemachine",
              "verdict": "confirmed",
              "reasoning": "I independently opened both files. OURS types.rs:703-711 inlines `let burden_mod = if burden < 1.0 {1.0} else if burden < 2.0 {2.0 - burden} else {0.0};` inside compute_jump_velocity_z. ACE EncumbranceSystem.cs:32-40 GetBurdenMod is `if (burden < 1.0f) return 1.0f; if (burden < 2.0f) return 2.0f - burden; else return 0.0f;`. The three branches are bit-identical: boundary conditions (<1.0, <2.0) and return values (1.0, 2.0-burden, 0.0) match exactly. The claim's prose \">=2.0 -> 0.0\" correctly maps to ACE's else branch (reached when burden>=2.0). Line refs are precise on both sides. No clamp, subdivision loop, extra term, or fallback was missed — the surrounding compute_jump_velocity_z context (power clamp, height formula, 0.35 min clamp, sqrt) is separate from the burden_mod branch the claim scopes. The kind=match / high-confidence classification is correct.",
              "corrections": "",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/player/types.rs:703-711 and /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Common/EncumbranceSystem.cs:32-40"
            }
          },
          {
            "id": "fall-damage-formula-sourced-from-ace",
            "statement": "compute_fall_damage is sourced verbatim from ACE Player_Move.HandleFallingDamage: jumpVelocity=11.25434, overspeed = jumpVelocity + currVz + 4.5, ratio = -overspeed/jumpVelocity, damage = ratio*87.293810 when ratio>0. NOT invented.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-world/src/player/types.rs",
              "lines": "654-665",
              "snippet": "const JUMP_VELOCITY: f32 = 11.25434; const LEEWAY: f32 = 4.5; const DAMAGE_SCALE: f32 = 87.293810;\nlet overspeed = JUMP_VELOCITY + landing_vz + LEEWAY;\nlet ratio = -overspeed / JUMP_VELOCITY;\nif ratio > 0.0 { ratio * DAMAGE_SCALE } else { 0.0 }"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/WorldObjects/Player_Move.cs",
              "lines": "254-272",
              "snippet": "var jumpVelocity = 11.25434f;\nvar overspeed = jumpVelocity + currVelocity.Z + 4.5f;\nvar ratio = -overspeed / jumpVelocity;\nif (ratio > 0.0f) { var damage = ratio * 87.293810f; ... }"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "jump-fall-statemachine/compute_fall_damage",
              "verdict": "confirmed",
              "reasoning": "I independently opened both files and verified every element of the claim. OURS crates/holtburger-world/src/player/types.rs:654-665 defines compute_fall_damage with JUMP_VELOCITY=11.25434 (655), LEEWAY=4.5 (656), DAMAGE_SCALE=87.293810 (657), overspeed = JUMP_VELOCITY + landing_vz + LEEWAY (658), ratio = -overspeed/JUMP_VELOCITY (659), and damage = ratio*DAMAGE_SCALE when ratio>0 else 0 (660-664). ACE Player_Move.cs:247-279 HandleFallingDamage uses var jumpVelocity = 11.25434f (254), overspeed = jumpVelocity + currVelocity.Z + 4.5f (259), ratio = -overspeed/jumpVelocity (261), and var damage = ratio*87.293810f inside if(ratio>0.0f) (268-271). All constants, operand order, the ratio sign, and the ratio>0 gate are byte-for-byte identical. The variable currVelocity.Z corresponds directly to OURS' landing_vz parameter. This is a genuine verbatim port, not invented. The kind=match classification is correct and not overstated.",
              "corrections": "Minor: the finder cited ACE Player_Move.cs:254-272, but the matched formula logic actually spans 254-271 — the damage = ratio*87.293810f constant is on line 271, and line 272 is a commented-out Console.WriteLine, not part of the logic. The range is still correct and inclusive of every relevant constant, so this does not change the verdict. Scope note (not an error): OURS' compute_fall_damage mirrors only the raw-damage formula and returns a float; it intentionally omits ACE's downstream TakeDamage_Falling (Player_Move.cs:282-309) bludgeon-resistance scaling + Math.Round, and ACE's redundant if(damage>0.0f) guard (line 277). The doc comment (types.rs:643-650) explicitly states the client function is documentation-only and that ACE applies damage server-side, so these omissions are deliberate and outside the claimed formula match. The doc's threshold of vz<-15.75 m/s is mathematically correct (overspeed crosses 0 at vz=-15.75434).",
              "checked": "crates/holtburger-world/src/player/types.rs:654-665 and 1018-1045; external/ACE/Source/ACE.Server/WorldObjects/Player_Move.cs:247-309"
            }
          },
          {
            "id": "fall-damage-never-applied-clientside",
            "statement": "compute_fall_damage has ZERO callers in production code (only its definition + unit tests reference it); the client never predicts/applies fall damage and defers entirely to the authoritative server, which in ACE applies it server-side via WeenieObject.DoCollision(EnvCollisionProfile) -> Player.HandleFallingDamage on environment collision.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-world/src/player/types.rs",
              "lines": "646-665",
              "snippet": "// **Client-side use is documentation only.** ACE applies fall damage server-side from PhysicsObj.Velocity ... (grep confirms no non-test callers of compute_fall_damage anywhere in the tree)"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Common/WeenieObject.cs",
              "lines": "235-238",
              "snippet": "if (wo is Player player) player.HandleFallingDamage(prof); else wo.OnCollideEnvironment();"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "jump-fall-statemachine",
              "verdict": "confirmed",
              "reasoning": "Independently verified every element of the claim by reading the source, not the finder's snippets. (1) Zero production callers: exhaustive grep for `compute_fall_damage` across .rs/.js/.ts returns only the definition at crates/holtburger-world/src/player/types.rs:654 and six call sites at lines 1022-1043 — all inside the `#[cfg(test)]` module that begins at line 873 (verified test-module boundary). The two other fall-damage grep hits in crates/holtburger-core/src/client/movement/system.rs (lines 950, 1421) are prose comments referencing ACE's HandleFallingDamage, not calls. No wasm_bindgen/no_mangle/extern export of the function exists, so there is no FFI/indirect caller either. (2) Client defers to server: the function's own doc comment (types.rs:643-650) explicitly states 'Client-side use is documentation only. ACE applies fall damage server-side.' (3) ACE path is real and current: WeenieObject.cs:223 declares `DoCollision(EnvCollisionProfile prof, ...)`, which at line 236 calls `player.HandleFallingDamage(prof)`; HandleFallingDamage(EnvCollisionProfile) is defined at Player_Move.cs:247, and the EnvCollisionProfile is created on environment collision at PhysicsObj.cs:3323. (4) The Rust mirror's constants/formula match ACE exactly: jumpVelocity 11.25434, +4.5 leeway, ratio = -overspeed/jumpVelocity, damage = ratio*87.293810 (Player_Move.cs:254-271). The divergence classification (client predicts nothing, server-authoritative on env collision) is accurate and correctly characterized, not overstated.",
              "corrections": "",
              "checked": "crates/holtburger-world/src/player/types.rs:654,873,1022-1043; crates/holtburger-core/src/client/movement/system.rs:950,1421; external/ACE/Source/ACE.Server/Physics/Common/WeenieObject.cs:223-241; external/ACE/Source/ACE.Server/WorldObjects/Player_Move.cs:247-280; external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:3323"
            }
          },
          {
            "id": "gravity-integration-euler-vs-kinematic",
            "statement": "Our airborne integrator uses symplectic Euler (update vertical_velocity by -9.8*dt, THEN add velocity*dt to z), whereas ACE's UpdatePositionInternal uses the kinematic form movement = Acceleration*0.5*quantum^2 + Velocity*quantum then Velocity += Acceleration*quantum. Both use g=9.8/Gravity=-9.8 but the position term differs by 0.5*a*dt^2 per tick.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/system.rs",
              "lines": "936-942",
              "snippet": "if world.player.is_airborne { let dt_s = dt.as_secs_f32(); world.player.vertical_velocity -= 9.8 * dt_s; pose.coords.z += world.player.vertical_velocity * dt_s; }"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "1854-1858",
              "snippet": "var movement = Acceleration * 0.5f * quantum * quantum + Velocity * quantum; frameOffset.Origin += movement; ... Velocity += Acceleration * quantum;"
            },
            "confidence": "high",
            "impact": "low",
            "verdict": {
              "claim_id": "jump-fall-statemachine",
              "checked": "crates/holtburger-core/src/client/movement/system.rs:936-942 and external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1854-1858 (plus PhysicsGlobals.cs:13, PhysicsObj.cs:2073-2084)",
              "verdict": "confirmed",
              "reasoning": "OURS system.rs:936-942 is symplectic Euler: vertical_velocity -= 9.8*dt then z += vertical_velocity*dt. It is the only airborne integrator in the movement crate; no 0.5*a*dt^2 term exists. ACE PhysicsObj.cs:1854-1858 uses the kinematic form movement = Acceleration*0.5*quantum^2 + Velocity*quantum, then Velocity += Acceleration*quantum. Gravity = -9.8 (PhysicsGlobals.cs:13) is loaded into Acceleration via calc_acceleration (2073-2084) when PhysicsState.Gravity is set, so it flows into line 1854. Constants and line refs are accurate and current. The per-tick position term differs by exactly 0.5*a*dt^2. Divergence classification at high confidence is correct.",
              "corrections": "Claim prose says UpdatePositionInternal but the kinematic code lives in UpdatePhysicsInternal, which UpdatePositionInternal (line 1862) calls at line 1881 — so the chain is correct and line refs 1854-1858 are accurate. ACE also zeros Acceleration on a walkable contact surface (lines 2075-2076), but that is the grounded case, not the airborne case, so it does not affect the conclusion.</corrections>\n</invoke>\n"
            }
          },
          {
            "id": "no-terminal-velocity-clamp",
            "statement": "Our integrator never clamps vertical_velocity, so a long fall accelerates unbounded; ACE clamps total velocity magnitude to MaxVelocity=50 m/s (and zeroes below SmallVelocity=0.25) inside UpdatePositionInternal.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/system.rs",
              "lines": "936-939",
              "snippet": "world.player.vertical_velocity -= 9.8 * dt_s;  // no upper bound on fall speed"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "1843-1852",
              "snippet": "if (velocity_mag2 > PhysicsGlobals.MaxVelocitySquared) { Velocity = Vector3.Normalize(Velocity) * PhysicsGlobals.MaxVelocity; ... }  (MaxVelocity = 50.0f)"
            },
            "confidence": "high",
            "impact": "low",
            "verdict": {
              "claim_id": "jump-fall-statemachine",
              "verdict": "confirmed",
              "reasoning": "The core divergence is real and accurate. OURS (system.rs:935-943): the airborne integrator does `vertical_velocity -= 9.8 * dt_s` then `pose.coords.z += vertical_velocity * dt_s` with NO clamp on vertical_velocity. I searched the entire holtburger crate tree — vertical_velocity is only ever set (begin_jump:733, begin_fall:757→0.0), decremented by gravity (938), or zeroed on land (768); there is no terminal-velocity / MaxVelocity / clamp anywhere (the grep for clamps returned only unrelated DAT-path matches). So a sustained fall does accelerate unbounded in our integrator. ACE side is also accurate: PhysicsGlobals.cs:30/34 confirm MaxVelocity=50.0f and SmallVelocity=0.25f. The clamp at PhysicsObj.cs:1843-1847 normalizes Velocity to MaxVelocity when velocity_mag2 > MaxVelocitySquared, and 1851-1852 zeroes Velocity below SmallVelocitySquared. This clamp bounds the FULL 3D velocity including the vertical component, because ACE drives gravity through Acceleration→Velocity (PhysicsObj.cs:2079-2080 sets Acceleration.Z = Gravity under PhysicsState.Gravity; line 1858 Velocity += Acceleration * quantum), so vertical fall speed is genuinely capped at 50 m/s in ACE. The divergence classification (our unbounded vs ACE bounded) is correct, not overstated.",
              "corrections": "One attribution error in the finder's evidence: the cited lines 1843-1852 are NOT inside UpdatePositionInternal — they live in UpdatePhysicsInternal (definition at PhysicsObj.cs:1832). UpdatePositionInternal (def line 1862) only CALLS UpdatePhysicsInternal at line 1881, so the clamp does run as part of the position-update path; the conclusion is unaffected, but the function name in the finder's evidence is wrong. Minor nuance: ACE's clamp also applies friction/SmallVelocity zeroing and works on total velocity magnitude — it is not a vertical-axis-specific clamp — but since gravity feeds Velocity.Z, it does effectively bound fall speed as the claim states.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/system.rs:935-943; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/player/types.rs:722-777; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1832-1884,2079-2080; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/PhysicsGlobals.cs:30-36"
            }
          },
          {
            "id": "jump-clip-exists-in-retail",
            "statement": "Retail player motion tables DO contain a Jump clip (MotionCommand 0x2500003B) for every player stance, either as cycles[(stance,Jump)] or links[(stance,Ready)][Jump], refuting the prior note that cmd_low 0x003B is absent from all motion tables. Our MotionCommandCode::JUMP = 0x2500003B matches ACE/acclient exactly.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-world/src/player/types.rs",
              "lines": "34, 738",
              "snippet": "pub const JUMP: u32 = 0x2500_003B; ... self.current_substate = MotionCommandCode::JUMP;"
            },
            "retail": {
              "file": "crates/holtburger-dat/examples/jump_clip_data_check.rs",
              "lines": "28-40",
              "snippet": "acclient.c -> MotionCommand::Jump = 0x2500003B; ACE -> MotionCommand.Jump = 0x2500003B; asserts at least one of cycles[(stance,Jump)] / links[(stance,Ready)][Jump] is non-empty for all 12 player stances (NEITHER_HITS=0 expected)"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "jump-fall-statemachine",
              "verdict": "partially-correct",
              "reasoning": "The claim has two parts with opposite truth values. PART 1 (constant equality) is CONFIRMED: MotionCommandCode::JUMP = 0x2500_003B at types.rs:34 and is assigned at types.rs:738 (self.current_substate = MotionCommandCode::JUMP in begin_jump). This matches ACE (Source/ACE.Entity/Enum/MotionCommand.cs:66 -> Jump = 0x2500003b) and the retail decomp (acclient_2013 pseudo-C: name-table mapping [0x03b] = 0x2500003b plus PhysicsObj jump comparisons against 0x2500003b). Line refs 34 and 738 are real and current. PART 2 (the headline data-presence claim and the 'refutes the prior note' conclusion) is REFUTED. I ran the finder's OWN cited evidence file, jump_clip_data_check.rs, against the real ~/ac_base_dats/client_portal.dat (926MB). Output: STANCES_WITH_JUMP=0, STANCES_MISSING_JUMP=12, NEITHER_HITS=12, OVERALL=FAIL; cross-check over all motion tables: TABLES_SCANNED=436, TABLES_WITH_JUMP=0, FIRST_WITH_JUMP=none. My independent probe of player MT 0x09000001 tested low-byte/low-16/low-20-bit AND the full 32-bit 0x2500003B against every cycle key and every link inner key: the ONLY 0x3B hit was inner=0x8000013B (an unrelated style/turn command), NOT Jump. The parser is healthy (366 cycles, 318 link outer keys parsed; encoding confirmed via sample keys like 0x003D00FC matching the example's (stance16<<16)|cmd20 packing). Therefore the 'prior note that cmd_low 0x003B is absent from all motion tables' is EMPIRICALLY CORRECT, and the finder's claim that it is refuted is itself refuted. The finder mistook the example's aspirational header (which documents what SHOULD pass on baseline DAT) for a passing result, without running it. The match classification is overstated: the constant matches, but the retail Jump clip is genuinely absent from the DAT, so the renderer's setMotion(Jump) link path (which the example warns is a no-op if no MT has Jump) does not resolve to motion-table data.",
              "corrections": "The data-presence assertion is false. Running the cited jump_clip_data_check.rs against the real client_portal.dat yields OVERALL=FAIL: zero stances have a Jump clip (cycles[(stance,Jump)] or links[(stance,Ready)][Jump]) and zero of all 436 motion tables contain any 0x003B Jump entry. The prior note (cmd_low 0x003B absent from all motion tables) is correct and is NOT refuted. The finder cited the example as proof without executing it; the example's STANCES_WITH_JUMP=12 / NEITHER_HITS=0 are expected-on-baseline targets that the real DAT does not meet. The 0x2500003B Jump motion command exists as a wire/enum constant and is set programmatically by begin_jump (types.rs:738), but it has no backing clip in the player motion table, so the kind=match data-presence framing is wrong — it should be a gap/divergence for the data layer.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/player/types.rs:34,738,121; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Entity/Enum/MotionCommand.cs:66; /home/wbterminal/ac-headers/acclient_2013.bndb_pseudo_c.txt:1017319 ([0x03b]=0x2500003b); ran /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-dat/examples/jump_clip_data_check.rs against ~/ac_base_dats/client_portal.dat -> OVERALL=FAIL, TABLES_WITH_JUMP=0"
            }
          },
          {
            "id": "falling-fallen-statemachine-invented",
            "statement": "Our begin_jump/begin_fall/land + is_airborne/is_jumping flags + Falling(0x40000015)/Fallen(0x40000008) wire emissions are a client-side visual state machine NOT present in retail/ACE, which carries only a single InterpretedState.ForwardCommand and uses set_on_walkable(false)/LeaveGround with no Falling/Fallen broadcast. The double-jump gate (is_airborne short-circuit) substitutes for retail's gravity-state/JumpExtent gating.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-world/src/player/types.rs",
              "lines": "722-762",
              "snippet": "pub fn begin_jump(&mut self, velocity_z: f32) { if self.is_airborne { return; } self.is_airborne = true; self.is_jumping = true; ... } pub fn begin_fall(&mut self) { ... self.current_substate = MotionCommandCode::FALLING; }"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs",
              "lines": "192-208, 710-727",
              "snippet": "public void LeaveGround() { ... var velocity = get_leave_ground_velocity(); PhysicsObj.set_local_velocity(velocity, true); ... } public WeenieError jump(...) { ... PhysicsObj.set_on_walkable(false); }  // no Falling/Fallen motion command emitted"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "jump-fall-statemachine",
              "verdict": "partially-correct",
              "reasoning": "Falling is emitted by retail and ACE so claim of absence is FALSE; state machine and is airborne gate parts are TRUE",
              "corrections": "Falling present in retail and ACE ours never emits Fallen our Falling is local animation only",
              "checked": "MotionInterp 481 acclient 344193 types 722 system 969"
            }
          },
          {
            "id": "js-predictor-z-omission",
            "statement": "The JS dead-reckon predictor in camera.js advances only X/Y from WASD input and never integrates Z, gravity, or jump arcs; it reads pose.z only for camera placement. All vertical jump/fall motion is owned solely by the wasm integrator, so the two layers do not duplicate or conflict on Z.",
            "kind": "match",
            "ours": {
              "file": "apps/holtburger-web/scene3d/camera.js",
              "lines": "1060-1070",
              "snippet": "this.predictedPlayerPos.x += Math.sin(effHeading) * speed * dtSafe; this.predictedPlayerPos.y += Math.cos(effHeading) * speed * dtSafe;  // predictedPlayerPos.z never written in the predictor"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "1854-1858",
              "snippet": "Single authoritative integrator advances all 3 axes incl. Z via Acceleration*0.5*q^2 + Velocity*q (no separate lateral-only predictor in retail)"
            },
            "confidence": "high",
            "impact": "low",
            "verdict": {
              "claim_id": "jump-fall-statemachine",
              "verdict": "confirmed",
              "reasoning": "Independently re-opened both cited files. OURS camera.js: the dead-reckon predictor `_advancePrediction` (lines 982-1099) only mutates predictedPlayerPos.x and .y — forward at 1060-1061, strafe at 1068-1069. There is no z term, no gravity, no jump arc anywhere in the WASD advancement path; the only inputs are sin/cos(heading)*speed*dt. So part 1 of the claim holds exactly. The z component of the predicted pose is written exclusively by reconciliation against the server pose: seed at line 881, teleport-snap at 907, lerp-target at 920 (`_reconcilePrediction`), and lerped at 1128 (`_applyPredictionLerp`). The server pose comes from window.__lastEntityWorldPos, which is the wasm-integrator output — so the JS layer never generates vertical motion, it only carries authoritative z forward. positionCamera (line 627 idealZ, 658 lookAt, 665/673/675 orbit+topDown targets) consumes p.z only for camera placement, and _safePlayerPos (1377-1390) reads z from the resolved player pose with an 80 m fallback — confirming part 2. ACE side: the cited PhysicsObj.cs lines 1854-1858 are the kinematic integrator `movement = Acceleration*0.5*quantum² + Velocity*quantum; frameOffset.Origin += movement`, applied to all three axes including Z. Gravity is injected into Acceleration.Z at lines 2079-2080 (`Acceleration = new Vector3(0,0,PhysicsGlobals.Gravity)` when PhysicsState.Gravity is set), so the cited line genuinely is the vertical jump/fall integrator the wasm port mirrors. All line refs, code forms, and the kind=match classification are accurate and current. The two layers do not duplicate or conflict on Z.",
              "corrections": "Minor incompleteness (does not change the verdict): the finder's wording \"reads pose.z only for camera placement\" understates that the JS layer also WRITES predictedPlayerPos.z — but only as reconciliation/lerp toward the authoritative server (wasm) pose in _reconcilePrediction (camera.js:881,907,920) and _applyPredictionLerp (camera.js:1128), never as independent integration. This actually reinforces the claim's conclusion (no duplication/conflict on Z) rather than refuting it. Also worth noting the finder cited a single ACE integrator line but the gravity source it depends on lives at PhysicsObj.cs:2079-2080, which is where Acceleration.Z is set; the cited 1854-1858 line is correct as the integration step.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/apps/holtburger-web/scene3d/camera.js:982-1099, 851-923, 603-677, 1377-1390; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1832-1860, 2079-2080"
            }
          }
        ],
        "open_questions": [
          "ACE's HandleFallingDamage reads PhysicsObj.Velocity.Z (world-frame, post-gravity-integration) at the moment of EnvCollisionProfile collision; I could not confirm from source whether our wasm integrator's vertical_velocity at landing matches that exact value ACE would see (our symplectic-Euler + no terminal clamp could diverge for tall falls), but since we never apply damage client-side this only matters for predicted-HUD parity, which we deliberately avoid.",
          "The 0.5m LEDGE_FALL_THRESHOLD_M used by begin_fall walk-off detection is a wasm-side heuristic tied to the 24m terrain-heightmap sample spacing; retail/ACE has no equivalent threshold (it uses continuous physics collision), so I could not source a retail value to validate it against — it is an admitted game-feel tuning constant.",
          "Our jump dispatch defaults PK=false for stamina cost because PKTimerActive is not tracked (lib.rs:33036-33038); ACE's JumpStaminaCost has a distinct PK branch ((power+1)*100). For an actual PK character our client-side stamina prediction would diverge from ACE until server reconciliation, but I could not confirm whether the wire Jump packet itself is affected (it carries extent, not the cost).",
          "I confirmed the Jump clip exists in retail motion tables via the jump_clip_data_check.rs audit's stated assertions, but did not execute it against a real client_portal.dat in this session to observe the actual STANCES_WITH_JUMP / NEITHER_HITS counts — the claim rests on the audit's documented expectations plus the acclient.c/ACE enum value, not a live data run."
        ]
      },
      {
        "dimension": "dual-predictor",
        "summary": "Our system runs two independent forward integrators for the local player where retail/ACE has exactly one (a single CPhysicsObj.Position store that both predicts and absorbs server corrections). The Rust/wasm integrator is a faithful port of retail: it derives run speed from Run skill + burden via run_rate_from_skill_and_burden (a 1:1 port of acclient.c MovementSystem::GetRunRate) and scales the player MotionTable's base run velocity by it. The SEPARATE JS dead-reckon predictor in camera.js (3D) and index.html (2D) instead advances X/Y with a HARDCODED RUN_SPEED=4.5 (and WALK=1.0) constant that does NOT read the integrator's per-tick speed — and structurally cannot, because the wasm LocalPlayerPose export carries no velocity/speed field, only x/y/z/heading/landblockId. The render path splits axes: loop.js applyLocalPlayerPoseFromIntegrator writes the rig from JS-predicted X/Y but integrator Z + integrator heading. Because the JS reconcile lerp target is the raw server pose (stashed into __lastEntityWorldPos at the server's slower skill-derived speed) while _advancePrediction overruns at 4.5, the two layers genuinely fight during the 150ms lerp for any character whose true run speed < 4.5 (e.g. a fresh char with Run≈0 actually moves at ~1.0 m/s server-side, a 4.5x mismatch) — producing a forward-overrun-then-lerp-back oscillation that the single-predictor retail client never exhibits. The get/set_last_client_prediction exports are a Wave 3.F diagnostic shadow only (consumed by diag/physics.js + validators), not the render path, and are fed only by the 2D rAF loop.",
        "claims": [
          {
            "id": "two-predictors-vs-one",
            "statement": "We run two independent forward integrators for the local player (Rust/wasm authoritative + JS dead-reckon in camera.js/index.html), whereas retail/ACE has a single CPhysicsObj.Position that both predicts via UpdatePositionInternal and absorbs corrections via PositionManager.InterpolateTo on the same store.",
            "kind": "divergence",
            "ours": {
              "file": "apps/holtburger-web/scene3d/loop.js",
              "lines": "223-292",
              "snippet": "function applyLocalPlayerPoseFromIntegrator(scene3d, sessionHandle){ ... const predicted = scene3d.cameraSwitcher.predictedPlayerPos; ... scene3d.entityManager.setPose(guid, predicted.x, predicted.y, renderZ, qw,0,0,qz); }  // X/Y from JS predictor, Z+heading from getLocalPlayerPose()"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "1655-1733",
              "snippet": "public void UpdateObjectInternal(double quantum){ var newPos = new Position(Position.ObjCellID); UpdatePositionInternal(quantum, ref newPos.Frame); ... var transit = transition(Position, newPos, false); ... SetPositionInternal(transit); ... if (PositionManager != null) PositionManager.UseTime(); }  // single Position store"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "dual-predictor",
              "verdict": "confirmed",
              "reasoning": "OURS side verified at the cited lines and surrounding code. (1) The wasm/Rust integrator is a genuine forward integrator with its own correction path: crates/holtburger-core/src/client/movement/system.rs:597 `advance_local_pose_for_manual_drive` integrates `world.local_player_runtime_pose()` from WASD intent, and system.rs:1158 `reconcile_server_controlled_projection` absorbs server corrections into that same wasm pose store. (2) The JS layer maintains a SECOND, independent forward integrator on a separate store `predictedPlayerPos`: camera.js:982 `_advancePrediction` advances `predictedPlayerPos.x/.y += sin/cos(heading)*speed*dt` from JS-side keystate, and camera.js:851 `_reconcilePrediction` absorbs server poses (from `__lastEntityWorldPos`, NOT from the wasm integrator's output) by snapping (>5m) or 150ms-lerping. loop.js:286-290 renders the rig from `predicted.x/.y` (JS store) — proving the JS store, not the wasm pose, drives the visible lateral position. So two distinct stores each run predict+correct loops. RETAIL/ACE side verified: PhysicsObj.cs:1655 `UpdateObjectInternal` predicts into `PhysicsObj.Position` via `UpdatePositionInternal`+`set_frame`/`SetPositionInternal`, and PositionManager.cs:55/119 `InterpolateTo`/`UseTime` delegate to InterpolationManager, which (InterpolationManager.cs:36,142,154,240) reads `PhysicsObj.Position` and writes corrections back through `PhysicsObj.SetPositionSimple/set_heading/set_velocity` — i.e. one single `CPhysicsObj.Position` store absorbs both prediction and interpolation corrections. Divergence classification is correct: we have dual independent integrators; ACE has one.",
              "corrections": "One nuance the finder understated (does not change the verdict): the JS integrator is independent only on the X/Y (lateral) axis. loop.js:260-274 sources Z and heading from the wasm integrator's `getLocalPlayerPose()`, and `_advancePrediction` (camera.js:1060-1069) only mutates `.x`/`.y`, never `.z`/heading. So it is not a fully 3-DOF second integrator — it is a dual integrator specifically for the user-visible lateral motion, with Z/heading shared from the wasm store. The 'JS dead-reckon in camera.js/index.html' phrasing is accurate for camera.js; the live 3D code path is in camera.js + loop.js (index.html holds the analogous 2D-path prediction the comments reference, ~6353-6420). The divergence-vs-ACE conclusion holds either way.",
              "checked": "OURS: apps/holtburger-web/scene3d/loop.js:223-292, apps/holtburger-web/scene3d/camera.js:851-923 and 982-1071, crates/holtburger-core/src/client/movement/system.rs:597-616 and 1158-1177 | ACE: external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1655-1733, external/ACE/Source/ACE.Server/Physics/Managers/PositionManager.cs:55-129, external/ACE/Source/ACE.Server/Physics/Managers/InterpolationManager.cs:36-240"
            }
          },
          {
            "id": "js-hardcoded-4.5-vs-skill-derived",
            "statement": "The JS predictor advances X/Y with a hardcoded RUN_SPEED=4.5 m/s (window.__movementConstants.FALLBACK_RUN_RATE_SCALAR ?? 4.5), whereas the Rust integrator and retail derive run speed from Run skill + burden — so for any sub-capped character the two speeds disagree (fresh char Run≈0,load≈0 → 1.0 m/s in Rust/retail vs 4.5 in JS, a 4.5x mismatch).",
            "kind": "divergence",
            "ours": {
              "file": "apps/holtburger-web/scene3d/camera.js",
              "lines": "1022-1061",
              "snippet": "const RUN_SPEED = consts.FALLBACK_RUN_RATE_SCALAR ?? 4.5; const WALK_SPEED = consts.WALK_FORWARD_SPEED ?? 1.0; ... let speed = run ? RUN_SPEED : WALK_SPEED; ... this.predictedPlayerPos.x += Math.sin(effHeading)*speed*dtSafe;"
            },
            "retail": {
              "file": "crates/holtburger-world/src/context.rs",
              "lines": "57-64, 200-203",
              "snippet": "pub fn run_rate_from_skill_and_burden(run_skill,burden){ if run_skill>=800.0 {18.0/4.0} else { (load_mod*(run_skill/(run_skill+200.0)*11.0)+4.0)/4.0 } }  // 1:1 port of acclient.c:713790 MovementSystem::GetRunRate; runskill 0 → 1.0 m/s"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "dual-predictor",
              "verdict": "partially-correct",
              "reasoning": "The core DIVERGENCE is real and even author-documented, but the finder's quantitative framing (\"1.0 m/s in Rust/retail vs 4.5 in JS, a 4.5x mismatch\") rests on a category error.\n\nVERIFIED TRUE:\n1. JS predictor hardcodes 4.5 as a final m/s used directly in X/Y advancement. camera.js:1023 `const RUN_SPEED = consts.FALLBACK_RUN_RATE_SCALAR ?? 4.5;` and camera.js:1055-1061 `let speed = run ? RUN_SPEED : WALK_SPEED; ... predictedPlayerPos.x += sin(effHeading)*speed*dtSafe;`. The constant is also defined at index.html:7635 `const FALLBACK_RUN_RATE_SCALAR = 4.5;` and exposed via window.__movementConstants (index.html:7647-7649). Line refs and code form are current and accurate.\n2. Rust derives run speed from Run skill + burden. context.rs:200-203 player_run_rate() = run_rate_from_skill_and_burden(run_skill, burden); context.rs:57-64 formula. For a fresh char run_rate_from_skill_and_burden(0,0) = (1.0*(0/(0+200)*11)+4.0)/4.0 = 1.0. This MATCHES retail exactly: MovementSystem::GetRunRate(load=0,runskill=0,scaling=1.0)=1.0 (acclient.c:713790-713802), including the >=800 -> 18/4 shortcut.\n3. The divergence is intentional and author-acknowledged. index.html:5934-5946 spells out the exact scenario: \"fresh characters whose Run skill is 0 mean the server-side run speed is effectively walking pace while the JS-side FALLBACK_RUN_RATE_SCALAR=4.5 m/s predicts a fast jog\" — and it's why UpdatePosition no longer snaps the sprite. So the predictors DO disagree for sub-capped chars; classification kind=divergence is correct.\n\nWHERE THE FINDER IS WRONG (magnitude):\nThe value 1.0 returned by run_rate_from_skill_and_burden / GetRunRate is NOT a final m/s speed — it is a run_RATE_SCALAR (a multiplier). Both Rust and retail compute final run speed = base_motion_table_run_velocity * run_factor. Rust: self_movement.rs:44-45 resolved_manual_run_speed = base_run_forward_speed() * run_rate_scalar; forward_axis_speed (common.rs:440) returns capabilities.resolved_manual_run_speed() for Run+Forward; this feeds local_velocity_for_state (common.rs:457-504) consumed by the X/Y integrator (system.rs:616, 1132). Retail: apply_run_to_command case 0x45000005 does `*speed = v5 * *speed` where v5=run_factor and *speed=base motion-table speed (acclient.c ~343450-343470). The player MotionTable RUN cycle velocity is ~2.5 m/s (test fixtures use 2.0-2.5). So a fresh char's Rust/retail run speed ≈ 2.5 * 1.0 ≈ 2.5 m/s, NOT 1.0 m/s, and the JS-vs-Rust gap is ~1.8x (4.5 vs ~2.5), not 4.5x. The finder conflated the scalar with the speed.\n\nADDITIONAL CONTEXT THE FINDER MISSED: 4.5 is NOT a JS-only hardcode. The Rust side defines the same const FALLBACK_RUN_RATE_SCALAR: f32 = 4.5 (common.rs:21) and uses player_run_rate().unwrap_or(4.5) (common.rs:147). So 4.5 is a shared cross-stack fallback for the run_rate_SCALAR slot — meaning if Rust's player_run_rate() returns None (skill data unavailable), Rust ALSO uses 4.5 as the scalar (then multiplies by base velocity, giving ~11 m/s). The real divergence is that JS treats 4.5 as a final speed while Rust treats it as a multiplier, and JS never reads the skill-derived run_rate at all.",
              "corrections": "Two substantive errors. (1) Magnitude: the finder says fresh-char Rust/retail speed is 1.0 m/s, giving a 4.5x mismatch. Wrong — 1.0 is the run_RATE_SCALAR (a multiplier), not the speed. Final Rust/retail speed = base_run_forward_velocity (player MotionTable RUN cycle, ~2.5 m/s) * run_factor(1.0) ≈ 2.5 m/s (self_movement.rs:44-45; retail apply_run_to_command `*speed = v5 * *speed`). True mismatch is ~1.8x (4.5 vs ~2.5), not 4.5x. (2) The finder implies 4.5 is a JS-only magic number; in fact the identical const FALLBACK_RUN_RATE_SCALAR=4.5 lives in Rust at common.rs:21 and is used as the run-rate-scalar fallback (common.rs:147 player_run_rate().unwrap_or(4.5)). The genuine divergence is that JS uses 4.5 directly as a final m/s with no base-velocity multiply and never consults the skill/burden formula, whereas Rust/retail use the skill-derived factor as a MULTIPLIER on the motion-table base velocity. Core divergence claim and kind=divergence classification stand; the quantitative figures are overstated/miscategorized.",
              "checked": "apps/holtburger-web/scene3d/camera.js:1022-1071; apps/holtburger-web/index.html:5934-5946,7635-7649; crates/holtburger-world/src/context.rs:47-64,200-203; crates/holtburger-core/src/client/movement/common.rs:21,146-148,437-455,457-504; crates/holtburger-world/src/state/self_movement.rs:35-107; /home/wbterminal/ac-headers/acclient.c:343450-343470,713790-713802"
            }
          },
          {
            "id": "predictors-fight-during-lerp",
            "statement": "The JS predictor and the slower server pose genuinely fight during the 150ms reconcile lerp: _advancePrediction overruns X/Y at 4.5 m/s each frame, then _reconcilePrediction targets the raw server pose stashed in __lastEntityWorldPos (which advances at the true skill-derived speed) and lerps predicted X/Y back over 150ms — a forward-overrun-then-pull-back oscillation retail's single predictor cannot produce.",
            "kind": "risk",
            "ours": {
              "file": "apps/holtburger-web/scene3d/camera.js",
              "lines": "913-922, 1118-1131",
              "snippet": "this._lerpTargetX = sx; this._lerpTargetY = sy; ... this._lerpRemainingMs = this._lerpDurationMs; ... predictedPlayerPos.x += (this._lerpTargetX - predictedPlayerPos.x)*frac;  // server pose target every fresh ts, fought by 4.5 advance"
            },
            "retail": {
              "file": "apps/holtburger-web/src/lib.rs",
              "lines": "28058-28069",
              "snippet": "entity_updates.borrow_mut().push(EntityUpdate{ kind: ENTITY_UPDATE_KIND_POSITION, guid: u32::from(data.guid), x: pos.coords.x, y: pos.coords.y, z: pos.coords.z, ... });  // local-guid KIND_POSITION carries the raw SERVER pos.coords → __lastEntityWorldPos → JS lerp target"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "dual-predictor",
              "verdict": "partially-correct",
              "reasoning": "The mechanism is real and I independently confirmed every load-bearing fact. _advancePrediction adds sin/cos(heading)*speed*dtSafe to predictedPlayerPos.x/y with speed = RUN_SPEED ?? 4.5 (camera.js:1023, applied 1060-1061), so the JS predictor advances X/Y at up to 4.5 m/s. _reconcilePrediction reads the server pose from window.__lastEntityWorldPos.get(guid) (camera.js:868-870), sets _lerpTargetX/Y/Z = sx/sy/sz and _lerpRemainingMs = _lerpDurationMs=150 (camera.js:918-921, 378), and _applyPredictionLerp pulls predicted toward that target each frame (camera.js:1118-1131). The tick order reconcile->advance->applyLerp (camera.js:589-591) means each frame the advance pushes predicted ahead and the lerp pulls it back toward the (slower) server pose — a genuine same-axis tug. The __lastEntityWorldPos slot really does carry the RAW server pose: it is fed by the wasm EntityUpdate{kind:POSITION, x: pos.coords.x...} at lib.rs:28058-28069, where pos = &data.pos.pos (the raw inbound UpdatePosition packet, lib.rs:27879), NOT the wasm runtime/predicted body pose (set_player_position is a no-op on body.pose during SimulatingMotionState, lib.rs:28004-28036). The speed-mismatch failure mode is explicitly documented in-repo: index.html:5934-5946 says low-run-skill characters leave the server pose lagging while the JS FALLBACK_RUN_RATE_SCALAR=4.5 m/s predicts a fast jog — the exact dual-predictor fight the claim describes. Retail (acclient) uses a single CPhysicsObj physics integrator whose speed derives from the real run rate, so it cannot exhibit a 4.5-m/s-vs-skill-speed tug. Knocked down from confirmed to partially-correct only because (a) the finder mislabels lib.rs:28058 as RETAIL/ACE evidence when it is OURS, and (b) 'oscillation' overstates the dynamics: predicted settles to a bounded steady-state forward OFFSET (per-frame sawtooth) staying ahead of the server pose, not a back-and-forth that crosses the target. The underlying risk is real.",
              "corrections": "1) The finder's RETAIL/ACE citation (lib.rs:28058-28069) is mislabeled — that file/line is OURS (the wasm emit that populates __lastEntityWorldPos), not retail or ACE source. It is the correct code to cite, but as the OURS side, not the comparison side; the finder provided no actual retail/ACE evidence for the 'single predictor' contrast. 2) 'oscillation' is slightly overstated: the advance(+4.5*dt)/lerp(pull-back) opposition produces a sustained bounded forward offset with per-frame sawtooth, with predicted remaining AHEAD of the server pose — not a sinusoidal back-and-forth crossing the target. 'Sustained fight / steady-state lag' is the precise characterization. 3) The constant is a fallback (consts.FALLBACK_RUN_RATE_SCALAR ?? 4.5); if window.__movementConstants supplies a skill-aware value the magnitude shrinks, but the index.html:7635 default is also hardcoded 4.5, so the fight still occurs in practice.",
              "checked": "apps/holtburger-web/scene3d/camera.js:851-922, 982-1071, 1118-1131, 378, 589-591; apps/holtburger-web/src/lib.rs:27878-27904, 28036, 28058-28069; apps/holtburger-web/index.html:5934-5946, 7635-7648; /home/wbterminal/ac-headers/acclient.c:6314-6392, 8238-8261"
            }
          },
          {
            "id": "no-speed-plumbing-to-js",
            "statement": "The JS predictor structurally cannot match the integrator's speed because the wasm getLocalPlayerPose() export (LocalPlayerPose) carries only x,y,z,heading,landblock_id — no velocity or speed field — so JS reads integrator heading but must guess speed from its own 4.5/1.0 constants.",
            "kind": "gap",
            "ours": {
              "file": "apps/holtburger-web/src/lib.rs",
              "lines": "20462-20469",
              "snippet": "pub struct LocalPlayerPose { x: f32, y: f32, z: f32, heading: f32, landblock_id: u32 }  // no velocity/speed field exposed to JS"
            },
            "retail": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "437-445",
              "snippet": "fn forward_axis_speed(state,capabilities){ (Gait::Run,Forward)=>capabilities.resolved_manual_run_speed(), ... }  // Rust integrator's actual per-tick run speed = base_run_forward_speed()*run_rate_scalar, never surfaced to JS"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "dual-predictor",
              "verdict": "confirmed",
              "reasoning": "The core claim is factually accurate and current. (1) The wasm export LocalPlayerPose (lib.rs:20463-20469) carries exactly x,y,z,heading,landblock_id and nothing else — verified at the struct def AND the constructor (lib.rs:26398-26404, built from world.local_player_runtime_pose() which discards velocity) AND the wasm-bindgen getters (lib.rs:20643-20674: only x/y/z/heading/landblockId are exposed). getLocalPlayerPose (lib.rs:22168-22171) returns that struct, so no velocity/speed crosses the boundary. (2) The JS rAF predictor in index.html does exactly what the claim says: it independently hardcodes FALLBACK_RUN_RATE_SCALAR = 4.5 (index.html:7635) and WALK_FORWARD_SPEED = 1.0 (index.html:7636), then at index.html:10053 picks `speed = run ? FALLBACK_RUN_RATE_SCALAR : WALK_FORWARD_SPEED` — it reads heading from the sprite/pose but guesses speed locally from the 4.5/1.0 constants. The JS comment at 7632-7634 even admits these are duplicated to \"mirror\" common.rs. (3) The 4.5/1.0 constants the finder cited are real: common.rs:21 `const FALLBACK_RUN_RATE_SCALAR: f32 = 4.5` and the 1.0 walk magnitudes in forward_axis_speed (common.rs:437-445, lines 441/443). The gap is in fact STRONGER than stated: the Rust integrator's actual run speed is resolved_manual_run_speed() (common.rs:440 → self_movement.rs:104-107), which uses a server-supplied run_rate_scalar (player_run_rate().unwrap_or(4.5), common.rs:146-147) and a motion-table-derived base walk speed — neither of which the flat JS 4.5/1.0 can know, because no speed field is published. So the JS predictor structurally cannot match the integrator's speed: confirmed. Classification kind=gap is correct.",
              "corrections": "One label error in the finder's evidence, not affecting the conclusion: it cited common.rs (crates/holtburger-core/src/client/movement/common.rs:437-445) under the \"RETAIL/ACE\" column. That file is OUR Rust code — the holtburger-core wasm-side integrator — NOT retail/ACE source. The actual ACE physics source lives at external/ACE/Source/ACE.Server/Physics and retail at ac-headers/acclient.c; neither was the cited file. So the comparison is really \"JS predictor vs OUR Rust integrator\" (a same-codebase dual-predictor divergence), which is precisely the gap dimension being tested — the mislabel does not change the verdict. Minor strengthening: the claim says JS \"must guess speed from its own 4.5/1.0 constants\"; in reality the Rust integrator can use a server-modified run_rate_scalar and motion-table-derived base speeds, so JS's flat 4.5/1.0 can diverge even when no guess error exists in the nominal case.",
              "checked": "apps/holtburger-web/src/lib.rs:20463-20674, 22168-22171, 26398-26404; apps/holtburger-web/index.html:7635-7636, 10049-10066; crates/holtburger-core/src/client/movement/common.rs:21,146-147,437-445; crates/holtburger-world/src/state/self_movement.rs:96-107"
            }
          },
          {
            "id": "axis-ownership-split",
            "statement": "Rig pose is composited across BOTH predictors by axis: X/Y come from the JS predictedPlayerPos, while Z and heading come from the Rust integrator's getLocalPlayerPose(); the camera's own height however reads the lagging predicted.z (only updated by the 150ms reconcile lerp, never integrated), so on hills the camera Z lags the rig Z.",
            "kind": "divergence",
            "ours": {
              "file": "apps/holtburger-web/scene3d/loop.js",
              "lines": "248-289",
              "snippet": "// _advancePrediction only advances X and Y on WASD; predicted.z only updates via the 150 ms reconcile lerp ... Sourcing Z straight from getLocalPlayerPose().z keeps the rig on the ground ... setPose(guid, predicted.x, predicted.y, renderZ, ...)"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "1664-1699",
              "snippet": "var newPos = new Position(Position.ObjCellID); UpdatePositionInternal(quantum, ref newPos.Frame); ... SetPositionInternal(transit);  // all 3 axes + heading from ONE integrated Frame, no per-axis split"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "dual-predictor",
              "verdict": "confirmed",
              "reasoning": "I independently traced every link of the claim in the current source rather than trusting the finder's snippets, and all of it holds.\n\nRIG POSE IS AXIS-COMPOSITED ACROSS TWO PREDICTORS (loop.js applyLocalPlayerPoseFromIntegrator): X and Y are taken from predicted.x / predicted.y (the cameraSwitcher.predictedPlayerPos, Workstream B JS predictor), while Z and heading come from the Rust integrator's sessionHandle.getLocalPlayerPose() (loop.js:260-274; setPose at 286-289 passes predicted.x, predicted.y, renderZ where renderZ is derived from the integrator z). Confirmed.\n\npredicted.z NEVER INTEGRATED, ONLY RECONCILE-LERPED: _advancePrediction (camera.js:982-1071) mutates ONLY predictedPlayerPos.x and .y (lines 1060-1061 forward, 1068-1069 strafe). The z field is written only by the reconcile seed (camera.js:884, 907) and the 150 ms lerp (target set 920, applied 1128-1129). _lerpDurationMs = 150.0 (camera.js:378). So on hills predicted.z lags actual altitude exactly as stated.\n\nCAMERA HEIGHT READS THE LAGGING predicted.z: the full chain is positionCamera (camera.js:603) -> idealZ = p.z + vertDist + 1.0 (line 627) where p = _safePlayerPos() (604) -> getPlayerWorldPos() (1379) -> production wiring entityManager.getLocalPlayerWorldPos() (index.js:1828) -> entities.js getLocalPlayerWorldPos (6338) which FIRST returns cs.getPredictedPlayerWorldPos() if non-null (6353-6356) -> getPredictedPlayerWorldPos returns predictedPlayerPos.z (camera.js:1146). So the camera's own height is sourced from the lagging predicted.z while the rig it follows is at the integrator's z. Confirmed, including the consequence (camera Z lags rig Z on slopes).\n\nDIVERGENCE CLASSIFICATION IS CORRECT: the ACE reference (PhysicsObj.UpdateObjectInternal / UpdatePositionInternal, lines 1655-1709) is a single unified integrator — it computes the entire newPos.Frame (X/Y/Z + heading quaternion) atomically in one pass and transitions/sets the whole frame together (set_frame 1672, SetPositionInternal 1699). There is no per-axis split and no separate camera-height source. Retail/server = one source of truth for the full pose; ours = X/Y from one predictor, Z/heading from another, and camera height from a third (lagging) value. Labeling this a kind=divergence with high confidence is accurate, not overstated.",
              "corrections": "None material. Two clarifications that strengthen rather than alter the claim: (1) The camera does not read predicted.z directly — it reads it through a four-hop indirection (positionCamera -> _safePlayerPos -> getPlayerWorldPos -> entityManager.getLocalPlayerWorldPos -> getPredictedPlayerWorldPos). The finder's wording (\"camera's own height reads the lagging predicted.z\") is correct on the net effect. (2) The rendered rig Z is not the raw integrator z but renderZ from getTerrainVisualZ (loop.js:285), a Catmull-Rom terrain raycast clamp layered on top of the integrator z; this is still integrator-anchored (the raycast seeds from posZ = integrator z and only lifts to the visual mesh if the cast hits), so it does not change the conclusion that rig Z tracks the live integrator altitude while camera Z lags.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/apps/holtburger-web/scene3d/loop.js:236-291; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/apps/holtburger-web/scene3d/camera.js:603-677,982-1071,1119-1148,1377-1390; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/apps/holtburger-web/scene3d/index.js:1824-1836; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/apps/holtburger-web/scene3d/entities.js:6338-6389; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1655-1709"
            }
          },
          {
            "id": "walk-1.0-is-live-constant",
            "statement": "The JS WALK_SPEED=1.0 (and RUN=4.5) is a live, shared value sourced from window.__movementConstants set in index.html (FALLBACK_RUN_RATE_SCALAR=4.5, WALK_FORWARD_SPEED=1.0), with the camera.js ?? fallbacks as a no-op duplicate; it is NOT a stale leftover — but it is a flat constant, not the integrator's MotionTable-derived base_walk_forward_speed.",
            "kind": "match",
            "ours": {
              "file": "apps/holtburger-web/index.html",
              "lines": "7635-7653",
              "snippet": "const FALLBACK_RUN_RATE_SCALAR = 4.5; const WALK_FORWARD_SPEED = 1.0; ... window.__movementConstants = { FALLBACK_RUN_RATE_SCALAR, WALK_FORWARD_SPEED, ... };  // single source of truth for 2D + 3D JS predictors"
            },
            "retail": {
              "file": "crates/holtburger-world/src/state/self_movement.rs",
              "lines": "36-46",
              "snippet": "pub fn base_walk_forward_speed(&self)->f32 { self.base_walk_forward_velocity.length() }  // integrator walk speed is MotionTable-derived, not a 1.0 constant"
            },
            "confidence": "high",
            "impact": "low",
            "verdict": {
              "claim_id": "dual-predictor",
              "verdict": "confirmed",
              "reasoning": "Every load-bearing assertion in the claim is real and current in the source today. (1) index.html:7635-7636 declares `FALLBACK_RUN_RATE_SCALAR = 4.5` and `WALK_FORWARD_SPEED = 1.0`, and lines 7647-7653 publish them on `window.__movementConstants` — a genuine single source of truth. (2) camera.js:1022-1024 reads them with `consts.FALLBACK_RUN_RATE_SCALAR ?? 4.5` and `consts.WALK_FORWARD_SPEED ?? 1.0`, exactly matching the published values, so the fallback literals duplicate (don't diverge from) the index.html source. (3) NOT stale: the constants are actively consumed by both predictors — the 2D path at index.html:10053-10077 and the 3D camera path at camera.js:1055-1069 (and the Rust wasm dead-code variant at lib.rs:25472). (4) The dual-predictor core: these flat JS constants are distinct from the integrator's MotionTable-derived kinematics. self_movement.rs:36-46 defines `base_walk_forward_speed()` (= `base_walk_forward_velocity.length()`) and `resolved_manual_run_speed()` (= base_run × run_rate_scalar); the live integrator at crates/holtburger-core/src/client/movement/common.rs:440-441 sources Walk/Run forward speed from those MotionTable-derived values, NOT from a flat 1.0/4.5. So the JS predictor (flat constants) and the Rust integrator predictor (MotionTable-derived) are genuinely separate — a true match/divergence to the retail-faithful model, classified correctly. Line refs in self_movement.rs (36-46) and index.html (7635-7653) are exact.",
              "corrections": "Minor overstatement: the finder calls the camera.js `??` fallbacks \"a no-op duplicate.\" They are not strictly never-hit — camera.js's own doc comments (lines 944-946 and 1016-1024) explicitly state `__movementConstants` can be undefined during the boot race before the login closure runs, in which case the `?? 4.5`/`?? 1.0` fallbacks ARE used. Because the fallback literals equal the published values, the runtime behavior is identical either way, so the substance of the finder's conclusion stands; \"no-op duplicate\" is just imprecise — it's better described as a redundant-but-reachable defensive fallback. This does not change the verdict. Also note the integrator's backstep/sidestep walk speed is itself hard-coded 1.0 in common.rs:443/453 (not MotionTable-derived), but the claim only concerns walk-forward/run-forward, which the integrator does derive from the MotionTable, so the claim remains accurate.",
              "checked": "apps/holtburger-web/index.html:7635-7653, apps/holtburger-web/index.html:10053-10077, apps/holtburger-web/scene3d/camera.js:1022-1069, crates/holtburger-world/src/state/self_movement.rs:36-46, crates/holtburger-core/src/client/movement/common.rs:437-444, apps/holtburger-web/src/lib.rs:25365-25472"
            }
          },
          {
            "id": "client-prediction-shadow-is-diag-only",
            "statement": "get_last_client_prediction / set_last_client_prediction are a Wave 3.F diagnostic shadow consumed only by diag/physics.js and the physics-replay validators — NOT the render path — and the setter is fed exclusively by the 2D rAF loop (localEntry.sprite.x/.y), so it does not couple the JS and Rust predictors at runtime.",
            "kind": "risk",
            "ours": {
              "file": "apps/holtburger-web/src/lib.rs",
              "lines": "22196-22237",
              "snippet": "/// get_local_player_pose returns the post-reconciliation pose ... THIS getter then returns whatever the integrator last wrote.  // pure-prediction shadow for OracleSim parity validators only"
            },
            "retail": {
              "file": "apps/holtburger-web/index.html",
              "lines": "10103-10122",
              "snippet": "handle.setLastClientPrediction(localEntry.sprite.x, localEntry.sprite.y, zEst, predVx, predVy, predVz, true, ...);  // fed only by the 2D sprite predictor; 3D camera.js never writes it"
            },
            "confidence": "high",
            "impact": "low",
            "verdict": {
              "claim_id": "dual-predictor",
              "verdict": "confirmed",
              "reasoning": "I independently re-read all cited code and traced every consumer. The claim holds on all substantive points. (1) Wave 3.F diagnostic shadow: lib.rs:22196-22216 doc comments explicitly mark get/set_last_client_prediction as the \"Wave 3.F (physics-replay parity, 2026-05-19)\" pure-prediction shadow for \"JS validators that need to compare the rAF integrator's output against the C# OracleSim.\" (2) Consumed only by diag/validators, NOT the render path: a repo-wide grep for getLastClientPrediction yields exactly two runtime consumers — scene3d/diag/physics.js:68-72 (the __diag.physics drift surface, which reads it as PREDICTED and compares against SERVER/APPLIED, never feeding rendering) and capture_physics_replay.cjs:370-371 (the replay validator). The 3D render path's camera uses a SEPARATE prediction source, cameraSwitcher.predictedPlayerPos (loop.js:257, camera.js:373), which is not fed by this getter. (3) Setter fed exclusively by the 2D rAF loop: there is exactly ONE setLastClientPrediction( call site (index.html:10112), inside the rAF integrator that reads/writes localEntry.sprite.x/.y; sprite is a PIXI.Sprite/Container (index.html:4911,5060,5074) — the default 2D PIXI render path. The setter args are localEntry.sprite.x / localEntry.sprite.y (index.html:10113-10114). (4) No runtime JS-Rust coupling: on the Rust side the last_client_prediction RefCell has exactly one writer (the setter, borrow_mut at lib.rs:22271) and one reader (the getter, borrow at lib.rs:22226); the recv-loop/integrator never touches it (confirmed by exhaustive grep + the lib.rs:24952-24953 comment \"The recv-loop never touches this cell\"). So it is a write-from-JS / read-by-JS-diag pass-through that cannot couple the JS rAF predictor and the Rust recv-loop predictor at runtime. The risk classification (diagnostic-only, no runtime coupling) is correct and not overstated.",
              "corrections": "One cosmetic labeling error in the finder's evidence, not affecting the conclusion: the finder put \"RETAIL/ACE: apps/holtburger-web/index.html:10103-10122\" in the ACE/retail slot, but index.html is OURS, not the retail decomp or ACE source. There is no genuine retail/ACE counterpart for this dimension because the dual-predictor comparison is between two of our own predictors (JS rAF integrator vs Rust recv-loop), both internal; the index.html block cited is the JS-side setter call site, which is correct content in the wrong slot. Minor citation precision: the getter body is lines 22224-22237 (doc comment begins 22196); the setter spans 22258-22278 — the finder's 22196-22237 range covers the getter + its doc but stops before the setter body, though both functions are clearly the subject.",
              "checked": "apps/holtburger-web/src/lib.rs:22196-22278 and 20309-20311, 24952-24957; apps/holtburger-web/index.html:9994-10128; apps/holtburger-web/scene3d/diag/physics.js:1-88; apps/holtburger-web/scene3d/loop.js:257; apps/holtburger-web/scene3d/camera.js:373"
            }
          },
          {
            "id": "preserve-gate-pins-integrator",
            "statement": "During active WASD the wasm preserve_local_runtime_pose gate makes routine server UpdatePosition broadcasts a no-op on body.pose (integrator keeps running at its own skill-derived speed), so getLocalPlayerPose()/integrator does NOT snap back — meaning the only thing dragging the rendered X/Y toward the slow server pose is the JS lerp, deepening the JS-vs-integrator disagreement rather than resolving it.",
            "kind": "risk",
            "ours": {
              "file": "apps/holtburger-web/src/lib.rs",
              "lines": "27920-27943",
              "snippet": "// during active simulation the integrator's mode is SimulatingMotionState so routine UpdatePosition broadcasts only refresh the auth pose, leaving the predicted runtime pose intact"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs",
              "lines": "824-825, 1733",
              "snippet": "MakePositionManager(); PositionManager.InterpolateTo(p, keepHeading); ... if (PositionManager != null) PositionManager.UseTime();  // retail corrections flow through the SAME Position via PositionManager, no second pose to preserve"
            },
            "confidence": "medium",
            "impact": "medium",
            "verdict": {
              "claim_id": "dual-predictor",
              "verdict": "partially-correct",
              "reasoning": "I independently re-opened the full chain in OURS and the cited ACE lines. The claim's MECHANISM premises are confirmed, but its load-bearing conclusion rests on a \"JS lerp\" that does not exist for the local player.\n\nCONFIRMED: (1) The preserve_local_runtime_pose gate (scene.rs:1230-1246) leaves body.pose untouched while updating only authoritative_pose/velocity/omega when mode is SimulatingMotionState|SimulatingVelocity. set_player_position -> update_player_position(Snapshot) -> reconcile is therefore a no-op on body.pose during active WASD (the wasm doc itself states this at lib.rs:28003-28006). (2) getLocalPlayerPose() returns the integrator pose, not the server pose: get_local_player_pose -> local_player_pose shadow -> publish_local_player_pose (lib.rs:26380) -> world.local_player_runtime_pose() -> runtime_pose_for_guid (mutations.rs:71-72) -> body.pose. So getLocalPlayerPose() genuinely does NOT snap back during active sim. (3) The integrator IS skill-derived: local_velocity_for_state -> resolve_self_movement_capabilities -> run_rate_from_skill_and_burden(SkillType::Run, burden) (context.rs:57-62). (4) A real dual-predictor speed disagreement exists: the JS step-3.5 predictor integrates the rendered sprite at FALLBACK_RUN_RATE_SCALAR=4.5 m/s (index.html:7635, 10053) while the wasm integrator uses the skill formula (~1.0 m/s for a Run=0 new char). (5) All cited line refs are real/current: lib.rs:27920-27943 is the gate doc block; ACE PhysicsObj.cs:824-825 (InterpolateTo) and :1733 (PositionManager.UseTime inside UpdateObjectInternal) exist as quoted.\n\nREFUTED CORE: The claim says \"the only thing dragging the rendered X/Y toward the slow server pose is the JS lerp.\" There is NO such JS lerp for the local player. handlePositionUpdate has an explicit no-snap policy for the local player (index.html:5924-5970): on a same-LB UpdatePosition it sets entry.lerpStartMs/lerpDurationMs = undefined and does NOT sync sprite.x/.y to the server pose at all. The ONLY lerp-seed sites (index.html:5972/5974/5976) are inside the non-local else branch. The local rendered X/Y is owned outright by the JS step-3.5 predictor (localEntry.sprite.x/.y at index.html:10060-10075), which reads only z from the wasm shadow (line 10106-10109), never x/y. So nothing routinely drags the local rendered X/Y toward the server pose during same-LB movement (convergence happens only on landblock crossing/teleport, line 5961-5967, or via force_position_sequence). The \"JS-vs-integrator disagreement deepened by a JS lerp\" narrative does not match the code.",
              "corrections": "The claim's central risk mechanism is wrong: there is no JS lerp dragging the local player's rendered X/Y toward the server pose. The lerp (index.html:5972-5976) is seeded ONLY in the non-local else branch; the local-player branch (5924-5970) explicitly DISABLES lerp (lerpStartMs/lerpDurationMs=undefined) and applies a deliberate no-snap policy, snapping only on landblock crossing. Also, the claim conflates two distinct predictors: the rendered local X/Y in the 2D path is owned by the JS step-3.5 predictor (localEntry.sprite.x/.y, 4.5 m/s constant), which is a SEPARATE predictor from the wasm integrator (body.pose, skill-derived). The JS predictor does not consume the wasm integrator's x/y at all (only z, line 10106-10109), so \"the integrator not snapping back deepens the JS-vs-integrator disagreement\" misdescribes the data flow. The confirmed facts (gate is a no-op on body.pose during active WASD; getLocalPlayerPose returns the integrator pose; genuine 4.5 vs ~1.0 m/s speed mismatch) are real, but the risk as stated overstates/misattributes the convergence path. The stale comment at index.html:6196-6197 (\"seeded from applyEntityUpdate's isLocal branch\") is itself misleading — the seed is actually in the non-local branch.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/spatial/scene.rs:1230-1246; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/state/mutations.rs:69-94,604-616; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/apps/holtburger-web/src/lib.rs:26376-26405,28003-28006; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/apps/holtburger-web/index.html:5922-6007,10049-10127; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/context.rs:57-63; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:822-825,1733"
            }
          }
        ],
        "open_questions": [
          "Does the REAL player MotionTable (DID 0x09000001, not the synthetic 0x09000020 test table) have base_run_forward_velocity.length() ≈ 1.0 so that resolved_manual_run_speed ≈ run_rate_scalar (making 4.5 numerically correct only for capped players)? The production motion_resolution path resolves it at runtime; I could not read the baked MotionTable bytes from source to confirm base_run is normalized to 1.0 rather than the 2.5 used in the unit-test fixture.",
          "In practice, what is the steady-state visible amplitude of the JS-vs-server overrun for a low-Run-skill character? At 4.5 vs 1.0 m/s with a 150ms lerp and ~33ms server-emit cadence, the predicted X/Y would overrun by up to ~(4.5-1.0)*0.033 ≈ 0.12m per emit and be pulled back each lerp — whether this reads as a steady forward bias, a visible jitter, or is masked needs a live capture (not resolvable from source).",
          "Does ACE's server actually advance the local player's authoritative pose at the skill-derived rate, or does it accept the client's faster heartbeat pose (no force_position) so __lastEntityWorldPos ends up reflecting the FAST client pose anyway? index.html:5934-5946 implies ACE 'happily accepts our heartbeats' at low Run skill while its authoritative pose stays near spawn — confirming the divergence — but whether the broadcast echoed back is the client heartbeat or the server's own slow integration determines whether the lerp target is fast or slow. Needs a wire capture to settle definitively.",
          "Is there a second, independent divergence between the 2D sprite predictor (index.html) and the 3D camera predictor (camera.js) given they share constants but use different heading conventions (SPRITE_HEADING_OFFSET=π/2 vs raw compass)? Out of scope for JS-vs-Rust but relevant to overall predictor consistency."
        ]
      },
      {
        "dimension": "motion-velocity-source",
        "summary": "Our Rust authoritative integrator derives base walk/run/turn speeds from the DAT MotionTable cycle velocity (base_run_forward_velocity.length() * run_rate_scalar), which structurally mirrors RETAIL's *actual* position-driving path — ACE/acclient apply `motionData.Velocity * speedMod` into the animation Sequence (MotionTable.add_motion/combine_motion → Sequence.apply_physics `frame.Origin += Velocity*quantum`). Retail's hardcoded anim constants (RunAnimSpeed=4.0, WalkAnimSpeed=3.12, SidestepAnimSpeed=1.25) live in `get_state_velocity`, which ACE calls ONLY from `get_leave_ground_velocity` (jump launch + a max-speed clamp), NOT for steady-state grounded locomotion. So the task's framing (\"ours DAT-derived vs retail hardcoded\") is half a red herring: BOTH systems are DAT-velocity-driven for grounded motion. The real, confirmable divergences are: (1) our run-rate scalar (faithful port of ACE GetRunRate, range ~1.0–4.5) multiplies a DAT magnitude whose equality to retail's effective speed I cannot verify from source (needs the real portal.dat player-MT cycle velocity); (2) we never implement retail's `get_state_velocity` normalize-and-clamp-to-`RunAnimSpeed*rate`, so our diagonal (geometric forward+sidestep sum, which DOES match retail's `CombinePhysics` additive composition) is uncapped where retail clamps the leave-ground velocity; (3) our `forward_axis_speed` backstep returns a bare `run_rate_scalar` (~4.5) for Run gait and `1.0` for Walk, diverging from retail's `WalkAnimSpeed*-BackwardsFactor` (3.12*0.65≈2.03) backstep; (4) the SEPARATE JS dead-reckon predictor and a dead-code wasm Rust path hardcode `RUN_SPEED=4.5`/`WALK_SPEED=1.0` — neither DAT-derived nor retail's 4.0/3.12 — so the visual predictor is a third, inconsistent speed source. FALLBACK_RUN_RATE_SCALAR=4.5 fires whenever player_run_rate() is None (Run skill not loaded), independent of MotionTable presence; the JS/dead-code paths use 4.5 unconditionally.",
        "claims": [
          {
            "id": "retail-grounded-speed-is-dat-derived-not-hardcoded",
            "statement": "Retail/ACE grounded steady-state locomotion velocity is driven by `motionData.Velocity * speedMod` (DAT cycle velocity scaled by the wire speed scalar), NOT by the hardcoded RunAnimSpeed=4.0/WalkAnimSpeed=3.12 constants; those constants live only in get_state_velocity which ACE calls solely from get_leave_ground_velocity (jump launch).",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-world/src/state/self_movement.rs",
              "lines": "44-58",
              "snippet": "pub fn resolved_manual_run_speed(&self, run_rate_scalar: f32) -> f32 { self.base_run_forward_speed() * run_rate_scalar }  // base_run_forward_speed() = base_run_forward_velocity.length() from DAT cycle"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Animation/MotionTable.cs",
              "lines": "358-385",
              "snippet": "public void add_motion(Sequence sequence, MotionData motionData, float speed){ sequence.SetVelocity(motionData.Velocity * speed); ... }  // Sequence.apply_physics: frame.Origin += Velocity * quantum"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "motion-velocity-source",
              "verdict": "partially-correct",
              "reasoning": "The CORE thesis is solid and independently verified. ACE grounded steady-state locomotion velocity is set in MotionTable.add_motion (MotionTable.cs:362 `sequence.SetVelocity(motionData.Velocity * speed)`) and combine_motion (:385 `CombinePhysics(motionData.Velocity * speed, ...)`), where `speed` = speedMod. I traced the steady-state entry point: MotionTableManager.PerformMovement (MotionTableManager.cs:124) handles MovementType.InterpretedCommand (normal Walk/Run) by calling `Table.DoObjectMotion(mvs.Motion, State, seq, mvs.Params.Speed, ...)`, so the grounded velocity is indeed the DAT cycle's MotionData.Velocity scaled by the movement/wire speed scalar (mvs.Params.Speed). The RunAnimSpeed=4.0/WalkAnimSpeed=3.1199999 constants are NOT on this path — they live in MotionInterp.cs, a different class, confirming the finder's central distinction. The finder's OURS ref (self_movement.rs:44-58, resolved_manual/autonomous_run_velocity = base_run_forward_velocity * scalar) and ACE ref (MotionTable.cs:358-385) are real and current.\n\nHowever, ONE supporting sub-claim is factually wrong. The claim states the constants 'live only in get_state_velocity.' They do not. I confirmed get_state_velocity (MotionInterp.cs:678) is indeed the sole consumer reached via get_leave_ground_velocity (:656 is the only caller of get_state_velocity) — that part holds. But the constants also appear OUTSIDE get_state_velocity: WalkAnimSpeed is used in apply_current_movement at MotionInterp.cs:421 (`speed *= SidestepFactor * (WalkAnimSpeed / SidestepAnimSpeed)`, a sidestep scaling on the live movement path), and both feed get_max_speed (:675 `return RunAnimSpeed * rate`), which is called from InterpolationManager.cs:221 and StickyManager.cs:112 as a speed clamp/ceiling. So the constants are not exclusively a jump-launch concern; one (WalkAnimSpeed) participates in steady-state sidestep speed shaping, and RunAnimSpeed defines the interpolation max-speed ceiling. The match/divergence classification of the central thesis is correct, but the scoping statement about the constants is overstated.",
              "corrections": "The clause 'those constants live only in get_state_velocity' is false. RunAnimSpeed/WalkAnimSpeed also appear in apply_current_movement (MotionInterp.cs:421, sidestep factor on the live grounded movement path) and in get_max_speed (MotionInterp.cs:675), which is called by InterpolationManager.cs:221 and StickyManager.cs:112 as a speed clamp. So they are not solely a jump-launch construct. The narrower statement 'get_state_velocity is called solely from get_leave_ground_velocity' IS correct (only caller at MotionInterp.cs:656). Also note the constants are defined in MotionInterp.cs:28/32 (WalkAnimSpeed=3.1199999f, not literally 3.12), in a different class than the cited MotionTable.cs — the finder cited MotionTable.cs:358-385 only for the motionData.Velocity*speedMod path, which is correct, but did not cite where the constants actually live, leaving the scoping claim unverified-by-the-finder and as it turns out partly wrong.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Animation/MotionTable.cs:358-385; MotionInterp.cs:28,32,200,421,654-700; MotionTableManager.cs:116-145; InterpolationManager.cs:221; StickyManager.cs:112; crates/holtburger-world/src/state/self_movement.rs:44-58"
            }
          },
          {
            "id": "get-state-velocity-is-jump-launch-only",
            "statement": "In ACE the hardcoded anim constants (1.25 sidestep, 3.12 walk, 4.0 run, maxSpeed=RunAnimSpeed*rate clamp) in get_state_velocity are invoked ONLY by get_leave_ground_velocity (MotionInterp.cs:200,656) — i.e. they set jump take-off velocity and a max-speed clamp, not the per-tick grounded run/walk speed; acclient.c:343821 confirms the same single call site.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "457-504",
              "snippet": "pub(super) fn local_velocity_for_state(...) -> Vector3 { // composes forward+sidestep from capabilities (DAT-derived), no 4.0/3.12/1.25 constants, no get_state_velocity-style normalize clamp"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs",
              "lines": "654-700",
              "snippet": "public Vector3 get_leave_ground_velocity(){ var velocity = get_state_velocity(); velocity.Z = get_jump_v_z(); ... } // get_state_velocity: 1.25*sidestep, 3.12*walk, 4.0*run, clamp to RunAnimSpeed*rate"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "motion-velocity-source",
              "verdict": "confirmed",
              "reasoning": "Every factual assertion in the claim holds against the source I independently opened.\n\nACE side (MotionInterp.cs): constants are defined verbatim — RunAnimSpeed=4.0f (L28), SidestepAnimSpeed=1.25f (L30), WalkAnimSpeed=3.1199999f (L32). get_state_velocity (L678-700) uses them: velocity.X = SidestepAnimSpeed*SideStepSpeed (L683), velocity.Y = WalkAnimSpeed*ForwardSpeed for walk (L685) / RunAnimSpeed*ForwardSpeed for run (L687), then maxSpeed = RunAnimSpeed*rate clamp via normalize+scale (L693-698). get_state_velocity has exactly ONE caller in the entire ACE Source tree: MotionInterp.cs:656, inside get_leave_ground_velocity. get_leave_ground_velocity (L654-663) sets velocity then velocity.Z = get_jump_v_z() (jump take-off), and is itself called only from LeaveGround (L200), the jump-take-off path. Cited line refs 200 and 656 are exact.\n\nDecomp side (acclient.c): get_state_velocity (343539) is byte-structurally identical — 1.25*sidestep_speed, 3.1199999*forward_speed, 4.0*forward_speed, and a maxSpeed clamp of run_rate*4.0 with normalize-and-rescale. It is called at exactly one site, 343821, inside get_leave_ground_velocity (343806), which is itself called once at 344478 inside LeaveGround. So acclient.c:343821 does confirm the single call site as stated.\n\nThe match classification is correct: in BOTH retail and ACE the hardcoded anim constants in get_state_velocity drive jump take-off velocity plus a max-speed clamp, not the per-tick grounded run/walk speed. I confirmed the contrast on OURS side: local_velocity_for_state (common.rs:457-504) is the grounded per-tick path (called from system.rs:616 and :1132, ongoing movement) and it sources speed from MotionTable-baked velocities — base_walk_forward_speed()/resolved_manual_run_speed() (self_movement.rs:36-46, 96-107), which return .length() of DAT-baked velocity vectors — NOT from any 1.25/3.12/4.0 literal. So the dimension finding (different velocity sources) is sound.",
              "corrections": "Minor scoping nuance, not an error in the claim as worded: the claim says the constants are \"invoked ONLY by get_leave_ground_velocity.\" That is exactly true of get_state_velocity (single call site). But the underlying constants are not exclusively a jump concept in ACE overall — RunAnimSpeed=4.0 is also used by get_max_speed (MotionInterp.cs:675), which feeds the clamp in StickyManager.cs:112 (get_max_speed*5.0) and InterpolationManager.cs:221 (get_max_speed*2.0), and WalkAnimSpeed/SidestepAnimSpeed are referenced in adjust_motion (MotionInterp.cs:421: speed *= SidestepFactor * (WalkAnimSpeed/SidestepAnimSpeed)). These are separate references to the same named constants, outside get_state_velocity, so they do not contradict the claim, but a reader should not infer these constants are touched only on the jump path. Also worth noting OURS' SIDESTEP_RUN_SPEED_CAP_M_PER_SEC=3.0 (common.rs:417) mirrors ACE's MaxSidestepAnimRate=3.0 (and retail's fabs>3.0 sidestep clamp), not the RunAnimSpeed*rate clamp — i.e. OURS' grounded sidestep cap corresponds to a different ACE constant than the get_state_velocity clamp the claim discusses.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs:28,30,32,200,421,654-700,675; /home/wbterminal/ac-headers/acclient.c:343539-343595,343806-343844,344478; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/common.rs:417,437-504; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/state/self_movement.rs:36-107"
            }
          },
          {
            "id": "run-rate-scalar-faithful-port",
            "statement": "Our run_rate_scalar is a faithful 1:1 port of ACE MovementSystem.GetRunRate: returns 18/4=4.5 for run_skill>=800 else (loadMod*(runSkill/(runSkill+200)*11)+4)/4, with identical burden_load_modifier thresholds; this scalar (range ~1.0–4.5) is the `speedMod`/run_factor retail multiplies into both apply_run_to_command and the Sequence velocity.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-world/src/context.rs",
              "lines": "57-64",
              "snippet": "if run_skill >= 800.0 { 18.0 / 4.0 } else { let load_mod = burden_load_modifier(burden); (load_mod * (run_skill / (run_skill + 200.0) * 11.0) + 4.0) / 4.0 }"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Animation/MovementSystem.cs",
              "lines": "20-28",
              "snippet": "if (runSkill >= 800) return 18.0f / 4.0f; return ((loadMod * ((float)runSkill / (runSkill + 200) * 11) + 4) / scaling) / 4.0f;"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "motion-velocity-source",
              "verdict": "confirmed",
              "reasoning": "Verified. OURS run_rate_from_skill_and_burden context.rs 57-64 and ACE GetRunRate MovementSystem.cs 24-27 share identical constants. burden_load_modifier context.rs 47-55 equals ACE GetBurdenMod EncumbranceSystem.cs 32-40. run_factor mechanism confirmed in decomp acclient.c 343447-343466 and Creature.cs 319. Match classification correct.",
              "corrections": "Named run_rate_from_skill_and_burden not run_rate_scalar. OURS omits ACE divide-by-scaling but all live callers pass 1.0 so it is a no-op. Walk uses fixed 1.5 not run_factor. None change verdict.",
              "checked": "context.rs:57-64 and MovementSystem.cs:20-28"
            }
          },
          {
            "id": "no-get-state-velocity-maxspeed-clamp",
            "statement": "Our local integrator has NO equivalent of retail get_state_velocity's normalize-and-clamp-to-(RunAnimSpeed*rate) step: only sidestep is capped (±3.0 in sidestep_axis_speed), the forward axis is uncapped, and the geometric forward+sidestep diagonal can exceed retail's clamped magnitude. Retail's clamp only applies on the leave-ground/jump path, so this primarily affects jump take-off fidelity, but the composed diagonal speed is structurally uncapped on our side.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "447-504",
              "snippet": "fn sidestep_axis_speed(...) { (Gait::Run, Some(_)) => capabilities.run_rate_scalar.min(SIDESTEP_RUN_SPEED_CAP_M_PER_SEC) }  // only sidestep clamped; local_velocity_for_state sums forward+sidestep with no combined clamp"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs",
              "lines": "689-699",
              "snippet": "var maxSpeed = RunAnimSpeed * rate; if (velocity.Length() > maxSpeed) { velocity = Vector3.Normalize(velocity); velocity *= maxSpeed; }"
            },
            "confidence": "medium",
            "impact": "medium",
            "verdict": {
              "claim_id": "motion-velocity-source",
              "verdict": "confirmed",
              "reasoning": "Every load-bearing element of the claim checks out against the source as it exists today, in both ACE and the retail decomp.\n\n(1) Retail/ACE normalize-and-clamp exists and clamps to RunAnimSpeed*rate. ACE MotionInterp.cs:693-698: maxSpeed = RunAnimSpeed * rate (RunAnimSpeed=4.0f, line 28); if velocity.Length() > maxSpeed it normalizes and rescales. Retail acclient.c:343586-343593 is the identical port (v9 = run_rate * 4.0; if sqrt(x^2+y^2+z^2) > v9, rescale). Forward uses 4.0 (run) / 3.12 (walk), sidestep uses 1.25 — same in both.\n\n(2) The clamp fires ONLY on the leave-ground/jump path. get_state_velocity has exactly one caller in each source: ACE MotionInterp.cs:656 (get_leave_ground_velocity), itself called only by LeaveGround() at line 200; retail acclient.c:343821 (get_leave_ground_velocity) called only by LeaveGround at 344478. I grepped both trees to confirm no other call sites. Normal ground movement in ACE goes through apply_interpreted_movement (MotionInterp.cs:440-504) which drives animation via DoInterpretedMotion and never composes/clamps a velocity vector.\n\n(3) Our side has no composed-magnitude clamp. common.rs:457-504 local_velocity_for_state geometrically sums forward (planar_velocity_for_heading at magnitude = resolved_manual_run_speed, UNCAPPED) + sidestep (capped only per-axis to ±3.0 via sidestep_axis_speed line 452). The function returns the raw geometric sum with no magnitude normalize-clamp. Both call sites (system.rs:616 integrator, system.rs:1132 solve-body) consume it directly.\n\n(4) Jump take-off also lacks the clamp on our side: system.rs:664-670 airborne branch passes target_velocity straight through (only Z handled by the gravity arc at 936-939). The per-axis accel-step clamp at system.rs:690 (delta.clamp(-accel_step, accel_step)) is a rate-of-change cap per axis, NOT a composed-magnitude normalize-clamp, and only runs on the grounded branch anyway.\n\nThe diagonal can genuinely exceed retail's clamp: forward run resolves to base_run_forward_speed*run_rate_scalar (self_movement.rs:44-46; fallback base ~4.5) and sidestep to run_rate_scalar.min(3.0), so sqrt(forward^2+sidestep^2) > 4.0*rate is reachable. Classification (kind=divergence, medium confidence) is accurate and correctly scoped.",
              "corrections": "No factual errors. Two sharpenings, neither weakens the claim: (a) On the GROUND path ACE/retail integrate no composed velocity at all — movement is animation-driven (apply_interpreted_movement -> DoInterpretedMotion, MotionInterp.cs:440-504) — so our ground integrator is itself a divergent design and there is no retail clamp to compare against there; the finder's narrower phrasing 'primarily affects jump take-off fidelity' plus 'structurally uncapped on our side' captures this correctly. (b) Our ±3.0 sidestep cap (common.rs:452, SIDESTEP_RUN_SPEED_CAP_M_PER_SEC) mirrors a DIFFERENT retail clamp (apply_run_to_command's run-scaled sidestep input clamp), not get_state_velocity's composed-magnitude clamp — consistent with the finder's point that we have nothing equivalent to the get_state_velocity step.",
              "checked": "crates/holtburger-core/src/client/movement/common.rs:437-504; crates/holtburger-core/src/client/movement/system.rs:616-709,936-939,1127-1144; crates/holtburger-world/src/state/self_movement.rs:36-66; external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs:192-208,440-504,654-700; /home/wbterminal/ac-headers/acclient.c:343539-343593,343806-343821,344457-344478"
            }
          },
          {
            "id": "backstep-speed-divergence",
            "statement": "Our backstep speed diverges from retail: forward_axis_speed returns a bare run_rate_scalar (~4.5 at cap) for (Run, Backstep) and 1.0 for (Walk, Backstep), whereas retail computes backstep as WalkAnimSpeed * -BackwardsFactor (3.12 * 0.65 ≈ 2.03 m/s) via adjust_motion converting WalkBackwards→WalkForward with speed *= -0.65, and backstep is NOT run-scaled in retail (no RunBackward clip).",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "437-445",
              "snippet": "(Gait::Run, Some(ForwardLocomotion::Backstep)) => capabilities.run_rate_scalar, (Gait::Walk, Some(ForwardLocomotion::Backstep)) => 1.0,"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs",
              "lines": "404-407",
              "snippet": "case (uint)MotionCommand.WalkBackwards: motion = (uint)MotionCommand.WalkForward; speed *= -BackwardsFactor;  // BackwardsFactor=0.65; later get_state_velocity uses WalkAnimSpeed=3.12"
            },
            "confidence": "medium",
            "impact": "medium",
            "verdict": {
              "claim_id": "motion-velocity-source",
              "verdict": "partially-correct",
              "reasoning": "The code citations are accurate and a real divergence exists, but the finder's characterization of retail backstep behavior is materially wrong on two counts.\n\nWHAT IS CORRECT:\n- OURS (common.rs:437-445) verified verbatim. forward_axis_speed returns capabilities.run_rate_scalar for (Run,Backstep) and 1.0 for (Walk,Backstep). This value is used DIRECTLY as a velocity magnitude (common.rs:488-492 -> planar_velocity_for_heading(heading, forward_speed)), and the path is local prediction (system.rs:616 target_velocity), not the wire (the wire correctly sends 1.0 and lets ACE rescale).\n- ACE (MotionInterp.cs:404-407) verified: adjust_motion rewrites WalkBackwards->WalkForward with speed *= -BackwardsFactor. Constants confirmed: BackwardsFactor=0.65 (line 26), WalkAnimSpeed=3.12 (line 32). Retail acclient.c:343774-343777 is bit-identical (-0.64999998, 0x45000006->0x45000005). get_state_velocity (ACE 684-685 / acclient.c:343561) gives velocity.Y = 3.12 * forward_speed for WalkForward. So Walk backstep = 3.12*0.65 = 2.028 m/s -- finder's 2.03 figure is right FOR WALK.\n- A genuine divergence exists: OURS uses run_rate_scalar (a dimensionless multiplier ~1.0-4.5, per run_rate_from_skill_and_burden at context.rs:57-64) as a raw m/s magnitude, whereas retail multiplies by 3.12*0.65.\n\nWHAT IS WRONG (the load-bearing error):\nThe claim asserts 'backstep is NOT run-scaled in retail.' This is FALSE. Retail/ACE apply_run_to_command (acclient.c:343463-343467 / MotionInterp.cs:539-544) for case WalkForward executes `*speed = run_factor * *speed` UNCONDITIONALLY. The `*speed>0.0` test only gates the swap to a RunForward CLIP; the speed multiply happens regardless of sign. Since the backstep speed is negative (-0.65) it keeps the WalkForward clip (finder right that there's no RunBackward clip) but the speed IS still scaled by run_factor. So retail Run+Backstep velocity = 3.12*0.65*run_factor = 2.028*run_factor m/s (clamped to 4.0*run_factor), NOT a flat 2.03 m/s. The finder conflated 'no run animation clip' with 'no run speed scaling'.\n\nNET EFFECT ON THE DIVERGENCE DIRECTION: OURS local prediction is too SLOW for backstep, not arbitrary. Retail Walk-backstep=2.028 m/s vs OURS 1.0 (2.03x slow); retail Run-backstep=2.028*run_factor vs OURS run_factor (2.03x slow). The divergence classification (kind=divergence) is correct, but the finder's retail target value (2.03 m/s flat) is only correct for the Walk case and understates the Run case by the run_factor.",
              "corrections": "1. False statement: 'backstep is NOT run-scaled in retail (no RunBackward clip).' Retail DOES run-scale backstep speed. acclient.c:343466 / MotionInterp.cs:543 multiply speed by run_factor unconditionally for the WalkForward case; only the clip-swap to RunForward is gated on speed>0. There is no RunBackward CLIP (true), but the speed magnitude IS run-scaled (the conclusion's premise is wrong). 2. The '3.12*0.65 ~= 2.03 m/s' retail target is the WALK backstep only. Retail Run+Backstep = 2.028*run_factor m/s (up to ~9.1 before the 4*run_factor magnitude clamp in get_state_velocity, acclient.c:343586-343593). 3. Minor framing: run_rate_scalar is a dimensionless multiplier (~1.0 at skill 0, capped 4.5 at skill>=800), not 'a speed ~4.5 at cap' -- OURS misuses this multiplier as a raw m/s magnitude, which is the actual mechanism of the bug.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/common.rs:437-455,479-492; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/state/self_movement.rs:36-66; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/context.rs:57-64,200-204; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs:26-32,404-407,525-561,678-699; /home/wbterminal/ac-headers/acclient.c:343439-343483,343539-343593,343746-343803"
            }
          },
          {
            "id": "js-predictor-third-speed-source",
            "statement": "The separate JS dead-reckon predictor (camera.js) and a dead-code wasm Rust path (lib.rs build_raw_motion_state_for_input) hardcode RUN_SPEED=FALLBACK_RUN_RATE_SCALAR=4.5 and WALK_SPEED=1.0 m/s — neither DAT-derived (like the authoritative integrator) nor retail's 4.0/3.12 constants — making the visual predictor a third, inconsistent speed source that ignores per-player run_rate and the MotionTable entirely.",
            "kind": "divergence",
            "ours": {
              "file": "apps/holtburger-web/scene3d/camera.js",
              "lines": "1022-1061",
              "snippet": "const RUN_SPEED = consts.FALLBACK_RUN_RATE_SCALAR ?? 4.5; const WALK_SPEED = consts.WALK_FORWARD_SPEED ?? 1.0; ... let speed = run ? RUN_SPEED : WALK_SPEED; this.predictedPlayerPos.x += Math.sin(effHeading) * speed * dtSafe;"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs",
              "lines": "678-693",
              "snippet": "if (...WalkForward) velocity.Y = WalkAnimSpeed * forwardSpeed; else if (...RunForward) velocity.Y = RunAnimSpeed * forwardSpeed;  // per-player rate via InqRunRate, never a flat 4.5/1.0"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "motion-velocity-source",
              "checked": "apps/holtburger-web/scene3d/camera.js:1022-1071; apps/holtburger-web/src/lib.rs:25461-25498; crates/holtburger-world/src/state/self_movement.rs:36-46,160-204; external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs:26-39,665-699",
              "verdict": "confirmed",
              "reasoning": "All factual elements verified in current source. (1) camera.js:1023-1024 hardcodes RUN_SPEED = FALLBACK_RUN_RATE_SCALAR ?? 4.5 and WALK_SPEED = WALK_FORWARD_SPEED ?? 1.0; lines 1050-1071 apply these flat magnitudes (backstep and strafe both 1.0); the predictor reads getLocalPlayerPose() only for heading, never run_rate/capabilities/MotionTable. (2) lib.rs:25462 build_raw_motion_state_for_input is genuinely dead: #[allow(dead_code)] at 25461, doc-comment 25378 calls it the legacy dead-code path, zero callers via grep. run_speed = if run { FALLBACK_RUN_RATE_SCALAR } else { 1.0 } (25472), backstep hardcoded 1.0 (25491). FALLBACK_RUN_RATE_SCALAR = 4.5 confirmed in common.rs:21 and lib.rs:25365. (3) ACE constants match: MotionInterp.cs:28 RunAnimSpeed=4.0f, :32 WalkAnimSpeed=3.1199999f, :30 SidestepAnimSpeed=1.25f; cited get_state_velocity 678-699 = AnimSpeed*ForwardSpeed capped at RunAnimSpeed*rate. (4) Authoritative integrator IS DAT-derived: self_movement.rs:168-185 resolves base_run_forward_velocity from resolve_player_motion_table_profile() (player MotionTable DID), and resolved_manual_run_speed = base_run_forward_speed()*run_rate_scalar (44-46), mirroring ACE RunAnimSpeed*rate. The flat-4.5/1.0 paths are a genuine third, MotionTable-and-run_rate-ignoring speed source. Divergence classification correct, not overstated.",
              "corrections": "Minor framing only, verdict unchanged: (a) the walk divergence is larger than the headline implies (predictor 1.0 vs retail 3.12), while run is 4.5 vs 4.0. (b) 4.5 is specifically the MAX-capped run-rate fallback per common.rs:14-21, used unconditionally by the predictor even though it is a last-resort fallback. (c) impact is bounded: predictor early-returns with no WASD held (camera.js:1008-1014) and the server reconcile drags predicted pose back each tick — this scopes the symptom but does not change that three inconsistent speed sources exist."
            }
          },
          {
            "id": "diagonal-geometric-sum-matches-combine-physics",
            "statement": "Our diagonal composition (local_velocity_for_state geometrically sums the forward and sidestep velocity vectors) matches retail's additive composition: Sequence.CombinePhysics does `Velocity += velocity`, so forward and sidestep contribute independently to one velocity vector that drives `frame.Origin += Velocity*quantum`. The combined magnitude can exceed each axis cap (e.g. √2 for walk+strafe) in both systems.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "477-503",
              "snippet": "let mut velocity = Vector3::zero(); ... velocity = velocity + planar_velocity_for_heading(heading, magnitude); ... velocity = velocity + planar_velocity_for_heading(sign_heading, sidestep_axis_speed(...));"
            },
            "retail": {
              "file": "external/ACE/Source/ACE.Server/Physics/Animation/Sequence.cs",
              "lines": "83-87",
              "snippet": "public void CombinePhysics(Vector3 velocity, Vector3 omega){ Velocity += velocity; Omega += omega; }"
            },
            "confidence": "medium",
            "impact": "medium",
            "verdict": {
              "claim_id": "motion-velocity-source",
              "verdict": "confirmed",
              "reasoning": "I independently re-opened all cited code and the underlying integrator chains rather than trusting the finder's snippets. OURS (common.rs:457-504, body 477-503) geometrically sums two heading-rotated planar vectors: forward contributes one vector, sidestep contributes a ±90°-rotated vector, accumulated via `velocity = velocity + planar_velocity_for_heading(...)`. The colocated unit test (common.rs:613-633) proves the √2 outcome: walk-forward(1.0)+strafe-right(1.0) at heading 0 = (-1.0, 1.0, 0.0), magnitude √2 — no combined-magnitude clamp in our path. ACE side is also accurate: Sequence.cs:83-87 `CombinePhysics` does `Velocity += velocity`, and the integrator at Sequence.cs:228 is `frame.Origin += Velocity * quantum`. I verified the dispatch the finder did NOT cite but which makes the claim true: MotionTable.cs add_motion (line 362) SETs velocity for the substate (forward) while combine_motion (line 385) → CombinePhysics ADDs the modifier (sidestep), so forward and sidestep accumulate into ONE Sequence velocity vector exactly as claimed. I cross-checked retail: acclient.c:339714-339722 `CSequence::combine_physics` does componentwise `velocity += v` (faithful source of ACE's port), and combine_motion (acclient.c:337477) scales DAT MotionData.velocity by speed_mod before combining. The retail reference in our code comment (acclient.c:332759-332786 InterpretedMotionState::ApplyMotion) genuinely carries independent forward_command/forward_speed and sidestep_command/sidestep_speed fields. The match classification is correct and not overstated: in both systems the combined diagonal magnitude (√2 ≈ 1.41 m/s for walk+strafe) exceeds each axis cap because composition is additive, not cap-then-project. Constants and line refs are real and current.",
              "corrections": "Two things the finder missed, neither of which changes the verdict: (1) ACE DOES have a velocity-magnitude clamp — PhysicsObj.cs:1843-1846 clamps to PhysicsGlobals.MaxVelocity (=50.0 m/s, PhysicsGlobals.cs:30) — but it operates on a DIFFERENT field (PhysicsObj.Velocity / m_velocityVector, the gravity/acceleration layer in UpdatePhysicsInternal), NOT Sequence.Velocity (the motion layer that CombinePhysics accumulates). At walking speed √2≈1.41 ≪ 50, so the clamp is a fallback never hit for this scenario; the claim's \"magnitude can exceed each axis cap\" stands. (2) ACE actually has two velocity layers (Sequence motion velocity + PhysicsObj.Velocity), so \"one velocity vector\" is precise only for the Sequence/animation layer where the diagonal composition lives; the world-space transform happens via AFrame.Combine(Position.Frame, offsetFrame) in UpdatePositionInternal (PhysicsObj.cs:1862-1881), since motion velocity is authored in object-local space. (3) Minor: the finder's evidence pointer to our integrator was imprecise — the actual consume site is system.rs:616 (local_velocity_for_state → target_velocity) and system.rs:717 (raw_delta = smoothed_planar * dt), not the line range implied by the surrounding comment.",
              "checked": "external/holtburger/crates/holtburger-core/src/client/movement/common.rs:477-503,613-633; external/ACE/Source/ACE.Server/Physics/Animation/Sequence.cs:83-87,221-230; external/ACE/Source/ACE.Server/Physics/Animation/MotionTable.cs:358-393; external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:1655-1738,1832-1884; external/ACE/Source/ACE.Server/Physics/PhysicsGlobals.cs:30-32; /home/wbterminal/ac-headers/acclient.c:337477-337503,339714-339722,332759-332786"
            }
          },
          {
            "id": "walk-run-motion-code-swap-matches",
            "statement": "Our walk→run code swap matches retail exactly: forward_command_for_state emits RUN_FORWARD (0x44000007) for (Run,Forward) and WALK_FORWARD (0x45000005) for (Walk,Forward), mirroring acclient.c apply_run_to_command which rewrites case 0x45000005 → 1140850695 (0x44000007) when speed>0 and HoldKey=Run; these are distinct DAT clips (run cycle vs walk cycle), not speed variants of one clip.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "175-185",
              "snippet": "(Gait::Run, ForwardLocomotion::Forward) => (RUN_FORWARD_MOTION_COMMAND, run_rate_scalar), (Gait::Walk, ForwardLocomotion::Forward) => (WALK_FORWARD_MOTION_COMMAND, 1.0),"
            },
            "retail": {
              "file": "/home/wbterminal/ac-headers/acclient.c",
              "lines": "343463-343469",
              "snippet": "case 0x45000005u: if ( *speed > 0.0 ) *motion = 1140850695; *speed = v5 * *speed; break;  // 1140850695 = 0x44000007 RunForward; v5 = run_factor"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "motion-velocity-source",
              "verdict": "confirmed",
              "reasoning": "Every load-bearing fact independently verified against the source today. (1) OURS: common.rs:175-185 — forward_command_for_state matches (Gait::Run, Forward)→(RUN_FORWARD_MOTION_COMMAND, run_rate_scalar) and (Gait::Walk, Forward)→(WALK_FORWARD_MOTION_COMMAND, 1.0). The constants are real: WALK_FORWARD_MOTION_COMMAND=0x4500_0005 (line 23), RUN_FORWARD_MOTION_COMMAND=0x4400_0007 (line 41). (2) RETAIL: acclient.c:343461-343467 — switch(*motion) case 0x45000005u: if(*speed>0.0) *motion=1140850695; *speed=v5**speed. I confirmed via python that 1140850695==0x44000007 exactly, and v5 is the run_rate (set from weenie InqRunRate or my_run_rate at 343453/343455). So retail rewrites WalkForward→RunForward when speed>0, then applies the run-rate scalar — exactly mirroring our (RUN_FORWARD, run_rate_scalar) emission. (3) The HoldKey=Run gate the finder cites lives one level up: ACE MotionInterp.cs:426-427 only calls apply_run_to_command when holdKey==Run; retail likewise. ACE's apply_run_to_command (MotionInterp.cs:537-544) is a 1:1 mirror of the decomp. (4) ACE accepts RunForward on the wire as a no-op: adjust_motion case RunForward: return (MotionInterp.cs:401-402). (5) Distinct-clips claim verified: MotionCommand.cs:12,14 give WalkForward=0x45000005 / RunForward=0x44000007; entities.js classifyMotionCommand (lines 854-859) masks cmd&0xffff and returns \"walk\" for 0x0005 and \"run\" for 0x0007, routing to separate AnimationCache cycle lookups (run cycle vs walk cycle) — not playback-rate variants of one clip. Line refs, constants, and the match classification are all accurate and current.",
              "corrections": "",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/common.rs:175-185 and :23,:41; /home/wbterminal/ac-headers/acclient.c:343461-343467; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs:401-402,426-427,537-544; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Entity/Enum/MotionCommand.cs:12,14; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/apps/holtburger-web/scene3d/entities.js:358,360,854-859"
            }
          },
          {
            "id": "sidestep-cap-matches-retail",
            "statement": "Our run-scaled sidestep ±3.0 m/s cap matches retail exactly: sidestep_axis_speed clamps run_rate_scalar to SIDESTEP_RUN_SPEED_CAP=3.0, mirroring acclient.c apply_run_to_command case 0x6500000F which sets *speed=±3.0 when fabs(run_factor*speed)>3.0 (sign-preserving), and ACE MotionInterp.cs:550-560 (MaxSidestepAnimRate=3.0).",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "447-455",
              "snippet": "(Gait::Run, Some(_)) => capabilities.run_rate_scalar.min(SIDESTEP_RUN_SPEED_CAP_M_PER_SEC),  // SIDESTEP_RUN_SPEED_CAP_M_PER_SEC = 3.0"
            },
            "retail": {
              "file": "/home/wbterminal/ac-headers/acclient.c",
              "lines": "343471-343481",
              "snippet": "case 0x6500000Fu: v6 = v5 * *speed; *speed = v6; if ( fabs(v6) > 3.0 ){ if ( v6 <= 0.0 ) *speed = -1.0 * 3.0; else *speed = 1.0 * 3.0; } break;"
            },
            "confidence": "high",
            "impact": "low",
            "verdict": {
              "claim_id": "motion-velocity-source",
              "verdict": "confirmed",
              "reasoning": "I independently opened all four cited sources and every load-bearing fact checks out. (1) OURS common.rs:447-455: sidestep_axis_speed for (Gait::Run, Some(_)) returns `run_rate_scalar.min(SIDESTEP_RUN_SPEED_CAP_M_PER_SEC)`, and the constant is `3.0` (line 417). (2) Retail acclient.c:343471-343481: CMotionInterp::apply_run_to_command case 0x6500000F computes `v6 = v5 * *speed` then `if (fabs(v6) > 3.0) { if (v6<=0) *speed=-3.0 else *speed=+3.0 }` — a sign-preserving ±3.0 clamp, exactly as the finder states. (3) ACE MotionInterp.cs:550-560: case SideStepRight does `speed *= speedMod; if (MaxSidestepAnimRate < Math.Abs(speed)) { if(speed>0) speed=+3.0 else speed=-3.0 }`, and MaxSidestepAnimRate=3.0f is real at MotionInterp.cs:27. I went past the finder's evidence to test the SEMANTIC equivalence (the part most likely to be wrong): in retail/ACE the clamp operates on `run_factor * speed` where the sidestep input speed is the unit ±1.0 magnitude (verified at common.rs:283-288, wire sidestep_speed = sign = ±1.0). Our run_rate_scalar IS the retail run_factor/InqRunRate value — run_rate_from_skill_and_burden (context.rs:57-64) mirrors ACE's GetRunRate and tops out at 18/4=4.5 — so `run_rate_scalar.min(3.0)` == `clamp(run_factor * 1.0)`. The equivalence is genuine, not coincidental. Sign in our code is applied separately via heading (common.rs:495-500), matching retail's sign-preserving clamp. The MotionCommand enum claim is also true: MotionCommand.cs:22-23 has only SideStepRight=0x6500000f / SideStepLeft=0x65000010, no RunSideStep. Line refs, constants, and the match classification are all accurate and current.",
              "corrections": "Only a cosmetic nit that does not affect the conclusion: the Rust constant is named SIDESTEP_RUN_SPEED_CAP_M_PER_SEC and a doc comment (common.rs:403) loosely calls the value 4.5 a \"run-speed (~4.5 m/s)\". In retail this 3.0 cap and the 4.5 run_factor are unitless anim-rate quantities (get_max_speed multiplies run_rate by 4.0 to reach m/s), not literally m/s. Since the sidestep input anim rate is exactly 1.0, the numbers and clamp behavior coincide 1:1, so the comparison verdict is unaffected — the naming/comment is just imprecise about units.",
              "checked": "/home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-core/src/client/movement/common.rs:417,447-455,283-288; /home/wbterminal/ac-headers/acclient.c:343461-343482; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs:27,550-560; /home/wbterminal/WorldBuilder-ACME-Edition/external/ACE/Source/ACE.Entity/Enum/MotionCommand.cs:22-23; /home/wbterminal/WorldBuilder-ACME-Edition/external/holtburger/crates/holtburger-world/src/context.rs:57-64,200-204"
            }
          }
        ],
        "open_questions": [
          "EMPIRICAL DAT MAGNITUDE: I could not read the real portal.dat player MotionTable (DID 0x09000001) cycle velocity magnitudes from source — the test fixtures use synthetic (1.0, 1.5) values. The whole equality question ('does ours' DAT-derived speed equal retail's effective m/s') hinges on whether |base_run_forward_velocity| ≈ 4.0 (matching RunAnimSpeed) or some other value (~1.0–1.5). If DAT velocity ≈ 4.0 AND retail grounded speedMod ≈ 1.0, they match; if DAT velocity ≈ 1.0 and our run_rate ≈ 2.65 is multiplied in, we'd be ~2.65 m/s vs retail's 4.0*~1.0 = 4.0 m/s. Needs a dump of the actual cycle velocity (WB.Terminal dump-lb / motion-table oracle).",
          "RETAIL GROUNDED speedMod: What value does retail actually pass as `speed` into add_motion/Sequence.SetVelocity for a steady-state grounded RunForward? apply_run_to_command multiplies the input speed (1.0) by run_factor, yielding ForwardSpeed ≈ run_rate (~2.65). If that same ForwardSpeed is then multiplied by DAT velocity in the Sequence, the effective grounded speed = DAT_velocity * run_rate — which is exactly our model. But I could not trace from acclient.c whether the grounded position update uses the Sequence velocity directly or re-derives via get_state_velocity. ACE clearly uses Sequence for grounded and get_state_velocity only for leave-ground; whether retail acclient.c does the same for the GROUNDED path (vs a different UpdatePositionInternal path) is unconfirmed.",
          "FORWARD+SIDESTEP SEQUENCE INTERACTION: In ACE apply_interpreted_movement, forward and sidestep are applied via SEPARATE DoInterpretedMotion→set_motion calls, and set_motion calls sequence.clear_physics() first (MotionTable.cs:152). It is unclear whether the sidestep's set_motion clears the forward velocity (so only one axis survives) or whether they accumulate via combine_motion into one diagonal. Our model assumes a clean geometric sum; CombinePhysics is additive but SetVelocity overwrites. If retail's per-axis set_motion overwrites, retail might NOT produce a true diagonal the way we do — this could be a divergence rather than the match I rated medium-confidence.",
          "DEAD-CODE STATUS: build_raw_motion_state_for_input (lib.rs:25462, RUN_SPEED=4.5) is marked #[allow(dead_code)] and the live wasm path routes through motion_state_for_input → the cli's build_motion_state_raw_motion_state (DAT-aware). But the JS predictor in camera.js (RUN_SPEED=4.5) is LIVE for the 3D visual. Confirming the JS predictor is purely cosmetic (server pose reconciles it) vs. ever authoritative for emitted position would clarify the real-world impact of its non-DAT 4.5 m/s."
        ]
      },
      {
        "dimension": "server-reconciliation",
        "summary": "Our autonomous-position contract is structurally faithful to retail: the 1-second heartbeat cadence, the four sequence stamps (instance / server-control / teleport / force-position), and the client-authoritative (autonomy-level-2) \"I report my own position, server vetoes\" design all match retail's CommandInterpreter and ACE's UpdatePlayerPosition. The sequence-gating logic (teleport-then-force-position newer-wins) is a correct port of retail's SmartBox::HandleReceivedPosition / ACE's should-accept logic. Where we diverge is the RECONCILIATION mechanism: retail smoothly InterpolateTo/ConstrainTo the local player toward a forced position (with 25 m indoor / 100 m outdoor blip thresholds for a hard snap), whereas our Rust integrator does NOT correct its working pose on an inbound force-position (it preserves the runtime pose via Snapshot mode), and the actual visual correction lives entirely in a separate JS predictor with a single 5 m hard-snap-or-150 ms-lerp rule. Two real risks follow: (1) a force-position under ~5 m never visually corrects and the wasm keeps re-sending the un-corrected drifted pose on the next heartbeat (the heartbeat is unconditional, lacking retail's position-changed gate), and (2) the indoor-rubberband history is plausible but the snap thresholds (our 5 m vs retail's 25 m indoor) are not matched, and the wasm authoritative pose is never reset by a sub-snap force-position.",
        "claims": [
          {
            "id": "heartbeat-cadence-1s-match",
            "statement": "Our autonomous-position heartbeat interval (1 second) exactly matches both retail's CommandInterpreter time_between_position_events (IEEE754 0x3FF0000000000000 = 1.0 s) and ACE's MoveToState_UpdatePosition_Threshold (1 second).",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "22",
              "snippet": "pub(super) const AUTONOMOUS_POSITION_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(1);"
            },
            "retail": {
              "file": "ac-headers/acclient.c",
              "lines": "717777-717778, 718121",
              "snippet": "LODWORD(v1->time_between_position_events) = 0; HIDWORD(...) = 1072693248;  // =1.0s\n... if ( v1->time_between_position_events + v1->last_sent_position_time < cur_time )  // ACE Player_Tick.cs:403 MoveToState_UpdatePosition_Threshold = TimeSpan.FromSeconds(1)"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "server-reconciliation-heartbeat-1s",
              "verdict": "partially-correct",
              "reasoning": "OURS and the RETAIL leg are fully confirmed; the ACE leg is overstated/misleading. (1) OURS: common.rs:22 `AUTONOMOUS_POSITION_HEARTBEAT_INTERVAL = Duration::from_secs(1)` — confirmed, and its only callers (system.rs:262, 1410) use it as the client-side autonomous position emit cadence, the correct analog. (2) RETAIL: acclient.c:717777-718 set `time_between_position_events` to a double with lo=0/hi=1072693248 = IEEE754 0x3FF0000000000000 = exactly 1.0s (I decoded it). The finder's hex constant and both line refs (717777-778 init, 718121 comparison) are exact and current. It IS the autonomous throttle: the constructor sets `autonomy_level = 2` (717749) and `ShouldSendPositionEvent` gates on `autonomy_level == 2` (718117) before the `time_between_position_events + last_sent_position_time < cur_time` test (718121). So our 1s genuinely and precisely matches retail's autonomous-position-event throttle — a strong, well-grounded match. (3) ACE leg is the problem. The finder cites `MoveToState_UpdatePosition_Threshold = TimeSpan.FromSeconds(1)` (Player_Tick.cs:403) — value is correct, BUT its own doc comment (Player_Tick.cs:395-396) says it throttles MoveToState UpdatePosition broadcasts and that \"AutonomousPosition still always broadcasts UpdatePosition\" — i.e. it explicitly does NOT govern autonomous positions, and it is a server-side broadcast gate (UpdatePlayerPosition:513), not the client emit cadence our constant models. More importantly, ACE's actual same-named port of the retail field, `CommandInterpreter.TimeBetweenPositionEvents` (CommandInterpreter.cs:32), is 1.875, NOT 1.0 — and it is dead code: ACE's `ShouldSendPositionEvent()` (CommandInterpreter.cs:793-796) is a stub returning `true`, never using the constant. So comparing the SAME mechanism across retail (1.0) and ACE (1.875) does NOT match; the finder's 1s ACE match is a coincidental value of a different, semantically-distinct constant.",
              "corrections": "The claim's framing that our 1s \"exactly matches both retail's time_between_position_events AND ACE's MoveToState_UpdatePosition_Threshold\" conflates two different ACE constants. (a) ACE's literal port of retail's time_between_position_events is CommandInterpreter.TimeBetweenPositionEvents = 1.875 (CommandInterpreter.cs:32) — it diverges from retail's 1.0, and is dead code (ShouldSendPositionEvent at CommandInterpreter.cs:793 is a `return true;` stub that never reads it). The finder did not surface this and it directly contradicts the \"exact match\" framing for the same mechanism. (b) MoveToState_UpdatePosition_Threshold (Player_Tick.cs:403) does equal 1s, but its doc comment (lines 395-396) states it governs MoveToState broadcasts and explicitly EXCLUDES AutonomousPosition (\"AutonomousPosition still always broadcasts UpdatePosition\"); it is a server-side broadcast throttle in UpdatePlayerPosition (line 513), not the client-emit autonomous cadence our constant represents. The OURS↔retail match is solid; the ACE leg is a same-value-different-mechanism coincidence, so the overall match classification is overstated.",
              "checked": "crates/holtburger-core/src/client/movement/common.rs:22; crates/holtburger-core/src/client/movement/system.rs:262,1410; ac-headers/acclient.c:717749,717777-717778,718108-718121; external/ACE/Source/ACE.Server/Physics/Command/CommandInterpreter.cs:32,793-796; external/ACE/Source/ACE.Server/WorldObjects/Player_Tick.cs:395-403,513"
            }
          },
          {
            "id": "client-authoritative-veto-design",
            "statement": "Our model is client-authoritative-with-server-veto (we send our own runtime pose as AutonomousPosition and only accept newer server force/teleport corrections), matching retail's autonomy_level==2 design where the client reports position and ACE validates/force-corrects via UpdatePlayerPosition.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/common.rs",
              "lines": "120-137",
              "snippet": "build_autonomous_position(...) { let position = world.local_player_runtime_pose()?; ... Some(AutonomousPositionActionData { position, instance_sequence, server_control_sequence, teleport_sequence, force_position_sequence, last_contact }) }"
            },
            "retail": {
              "file": "ac-headers/acclient.c",
              "lines": "44837, 718116-718124",
              "snippet": "command_line_autonomy_level = 2u; ... if (... && v1->autonomy_level == 2 ...) { result = last_sent_position != m_position || !Frame::is_equal(...) }  // ACE Player_Tick.cs:411 UpdatePlayerPosition validates client RequestedLocation"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "server-reconciliation",
              "verdict": "confirmed",
              "reasoning": "Every part of the claim verified against current source. OURS common.rs:124 — build_autonomous_position sends world.local_player_runtime_pose() (the client's own predicted pose), so we report our runtime position, not the server pose: client-authoritative reporting confirmed. The server-veto half is real and concrete: apply_player_autonomous_position (mutations.rs:658-708) gates every incoming self-correction through should_accept_server_position_sequences (player/mutations.rs:226-242), which rejects when the incoming teleport_sequence is older, or when teleport epochs are equal and force_position_sequence is older, using wrap-aware is_newer_u16 (sequence.rs:6-9). So we only accept NEWER server force/teleport corrections — the wording is precise, not overstated. RETAIL acclient.c:44837 is command_line_autonomy_level = 2u (exact), and acclient.c:718116-718124 is CommandInterpreter::ShouldSendPositionEvent gating on autonomy_level == 2 (line 718120) — the client only reports position in autonomy level 2, matching our AutonomousPosition reporting. ACE side: SetRequestedLocation (Player_Networking.cs:300, comment 'Records where the client thinks we are') stores the client-reported pose; UpdatePlayerPosition (Player_Tick.cs:411) ValidateMovements it, runs MaxSpeed and z-pos-hack checks, and on failure force-corrects by resetting Location, calling Sequences.GetNextSequence(SequenceType.ObjectForcePosition) and SendUpdatePosition() (lines 486-490) — i.e. it bumps exactly the force-position sequence our client treats as a newer accepted correction. The architectural match (client-authoritative-with-server-veto / autonomy_level==2) is accurate and correctly classified.",
              "corrections": "Minor citation-locality nit, not a factual error: the finder named ACE's UpdatePlayerPosition but its only concrete ACE/retail line refs were acclient.c (the retail decomp) and the path ACE/.../Physics. The actual ACE UpdatePlayerPosition / ValidateMovement / ObjectForcePosition force-correction lives in ACE.Server/WorldObjects/Player_Tick.cs:411-491 and SetRequestedLocation in Player_Networking.cs:300, not under the Physics dir. The function is real and behaves exactly as the claim states; only the cited directory for it was imprecise. Also worth noting: the same veto helper backs the non-autonomous UpdatePosition path too (apply_position_from_server, player/mutations.rs:246), so the mechanism is broader than just AutonomousPosition — this strengthens rather than weakens the claim.",
              "checked": "crates/holtburger-core/src/client/movement/common.rs:120-137; crates/holtburger-world/src/state/mutations.rs:658-708; crates/holtburger-world/src/player/mutations.rs:226-242; crates/holtburger-common/src/sequence.rs:6-9; ac-headers/acclient.c:44837; ac-headers/acclient.c:718116-718124; ACE/Source/ACE.Server/WorldObjects/Player_Tick.cs:411-491; ACE/Source/ACE.Server/WorldObjects/Player_Networking.cs:300-304"
            }
          },
          {
            "id": "sequence-gating-port-correct",
            "statement": "Our inbound position-acceptance gate (reject if our teleport_sequence is newer; within same teleport epoch reject if our force_position_sequence is newer) is a faithful port of retail's SmartBox::HandleReceivedPosition gating (newer_event FORCE_POSITION_TS + teleport-timestamp compare) and ACE's sequence ordering.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-world/src/player/mutations.rs",
              "lines": "226-242",
              "snippet": "fn should_accept_server_position_sequences(...) { if is_newer_u16(self.teleport_sequence, teleport_sequence) { return false; } if teleport_sequence == self.teleport_sequence && is_newer_u16(self.force_position_sequence, force_position_sequence) { return false; } true }"
            },
            "retail": {
              "file": "ac-headers/acclient.c",
              "lines": "145158-145164, 145196",
              "snippet": "if ( object != v11 || !CPhysicsObj::newer_event(v11, FORCE_POSITION_TS, force_position_timestamp) || (teleport_timestamp compare) ) { ... if ( CPhysicsObj::newer_event(object, TELEPORT_TS, teleport_timestamp) ) SmartBox::TeleportPlayer(...) }"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "checked": "mutations.rs:226-260 sequence.rs:6-9 acclient.c:145157-145167,145196,143015-143038 acclient.h:6084-6097 entity.rs:344-377 tests.rs:1026-1089 WorldObject_Networking.cs:411-419",
              "claim_id": "server-reconciliation-position-acceptance-gate",
              "verdict": "partially-correct",
              "reasoning": "The primitive matches but the gating algebra does not. is_newer_u16(new,stored) equals retail newer_event(stored,new) for all u16 pairs except the 0x8000 tie (brute-forced). Slot 4 is TELEPORT_TS, slot 6 is FORCE_POSITION_TS. Retail outer-gate reject is (fp newer) AND (tp not newer), while our Rust reject is (stored tp newer than incoming) OR (same teleport epoch AND stored fp newer). Teleport is our primary key so any stale-teleport packet is rejected outright. These conditions disagree on many inputs. Example: stored tp 103 fp 101, incoming tp 100 fp 100, our Rust rejects but retail enters the body because fp not newer makes the outer OR true. They only coincide on the canonical stale cases the tests cover. Ingredients mirrored but composition is a different algorithm, so faithful port overstates fidelity.",
              "corrections": "Three things missed. One, the boolean composition differs structurally. Two, retail outer newer_event on FORCE_POSITION_TS has a side effect that advances the stored fp slot even when the body is skipped, whereas our predicate is pure and slots advance only on accept. Three, retail teleport-vs-interpolate decision lives in inner newer_event calls at 145167 and 145196, not the outer gate, while ours collapses to one outer accept-reject. The ACE part adds nothing since ACE is the server and only emits sequences monotonically with no client-side gate."
            }
          },
          {
            "id": "forced-reposition-no-smooth-reconcile",
            "statement": "Retail smoothly reconciles the local player toward a forced position via PositionManager InterpolateTo/ConstrainTo (only teleport zeroes velocity and hard-sets; a force-position under the blip distance interpolates), whereas our wasm integrator does NOT smoothly reconcile or even correct its working pose on a force-position — set_player_position uses Snapshot mode which preserves the runtime pose while mid-simulation.",
            "kind": "divergence",
            "ours": {
              "file": "crates/holtburger-world/src/spatial/scene.rs",
              "lines": "1230-1246",
              "snippet": "let preserve_local_runtime_pose = matches!(body_id, SpatialBodyId::LocalPlayer(_)) && matches!(sync, AuthoritativeBodySync::Snapshot) && matches!(body.sampling.mode, SimulatingMotionState | SimulatingVelocity); ... if !preserve_local_runtime_pose { body.pose = pose; body.sampling.mode = mode; }"
            },
            "retail": {
              "file": "ac-headers/acclient.c",
              "lines": "145208-145220",
              "snippet": "else { CPhysicsObj::ConstrainTo(v10->player, &pos, start, max); if (...autonomy...) { if (has_contact) { ...; CPhysicsObj::InterpolateTo(object, &pos, keep_heading); } } }"
            },
            "confidence": "high",
            "impact": "high",
            "verdict": {
              "claim_id": "server-reconciliation-divergence",
              "verdict": "confirmed",
              "reasoning": "I independently re-opened all cited code in both trees and the claim holds. RETAIL (acclient.c:145165-145250, function SmartBox::HandleReceivedPosition): the teleport branch (145196-145206) calls TeleportPlayer + ConstrainTo + set_velocity with a zeroed Vector3 (teleport hard-sets and zeroes velocity). The non-teleport force-position branch (145208-145220) calls ConstrainTo and, gated on has_contact plus a cmdinterp vfptr check, calls CPhysicsObj::InterpolateTo (smooth interpolation, no velocity zero). ACE corroborates: PhysicsObj.cs:325/822 delegate ConstrainTo/InterpolateTo to PositionManager, and MoveOrTeleport (PhysicsObj.cs:905-934) interpolates when PlayerDistance < 96.0f else snaps, confirming the blip-distance gate. OURS: the UpdatePosition message (carrying both teleport_sequence and force_position_sequence) always routes handlers/player.rs:42 -> set_player_position -> update_player_position(pos, AuthoritativeBodySync::Snapshot) (mutations.rs:606) -> reconcile_authoritative_body(..., Snapshot) (scene.rs:1211-1248). At scene.rs:1230-1246, for a LocalPlayer mid-sim with Snapshot, preserve_local_runtime_pose is true so the `if !preserve_local_runtime_pose { body.pose = pose }` write is skipped: the working pose is kept untouched, no interpolation toward and no clamp to authoritative. There is NO blip-distance logic at all: snap_distance_meters()/snap_heading_radians() (types.rs:194-200) are defined but have ZERO consumers. So OURS never smooth-reconciles a force-position. Divergence classification is correct.",
              "corrections": "Two clarifications that strengthen the claim. (1) The finder cites OURS at scene.rs:1230-1246 (the preserve_local_runtime_pose block), correct downstream evidence, but the entry that selects Snapshot is set_player_position at mutations.rs:606, called from handlers/player.rs:42 for the UpdatePosition message. (2) Snapshot is not unconditionally pose-preserving: when the local body is NOT mid-simulation (mode AuthoritativeOnly), scene.rs:1243 DOES hard-set body.pose = pose (a snap, not a smooth lerp). So OURS hard-snaps in the idle case and preserves-without-correction in the simulating case; in neither case does it InterpolateTo like retail, which is exactly the asserted divergence.",
              "checked": "crates/holtburger-world/src/spatial/scene.rs:1211-1248 and 1230-1246, crates/holtburger-world/src/state/mutations.rs:604-624, crates/holtburger-world/src/handlers/player.rs:33-47, crates/holtburger-world/src/spatial/types.rs:182-201, ac-headers/acclient.c:145165-145250, external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:905-934"
            }
          },
          {
            "id": "snap-threshold-mismatch",
            "statement": "Our only visual force-position correction is a JS-side single 5 m hard-snap (else a 150 ms lerp), but retail's snap/interpolate decision uses autonomy blip distances of 25 m indoor / 100 m outdoor for the player and constraint start distances of 5 m indoor / 10 m outdoor — so our 5 m snap threshold is far tighter than retail's and not landblock/indoor-aware.",
            "kind": "divergence",
            "ours": {
              "file": "apps/holtburger-web/scene3d/camera.js",
              "lines": "901-911",
              "snippet": "// Snap on large delta (teleport, rubberband, init). The 5 m threshold...\n if (dist > 5.0) { this.predictedPlayerPos.x = sx; ...; this._lerpRemainingMs = 0.0; return; }"
            },
            "retail": {
              "file": "ac-headers/acclient.c",
              "lines": "315861-315880, 144717-144719",
              "snippet": "GetAutonomyBlipDistance: player indoor(objcell>=0x100)=25.0 else 100.0 ... PlayerPositionUpdated: a2 = GetAutonomyBlipDistance(v5); if (a2 >= distance_moved) goto LABEL_6;  // BlipPlayer(=SetPositionSimple hard snap) at 145246"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "server-reconciliation-snap-threshold",
              "verdict": "confirmed",
              "reasoning": "Both citations are real and current. OURS: camera.js:904 is exactly `if (dist > 5.0)` for a hard-snap, and the else branch starts a lerp using `_lerpDurationMs = 150.0` (defined :378, applied :921). `_reconcilePrediction` (:851-923) has zero landblock/indoor awareness — the 5 m threshold is a flat literal with a comment citing \"the spec\" and worst-case single-tick drift, not objcell_id. RETAIL: acclient.c:315861-315880 GetAutonomyBlipDistance returns player 25.0 indoor (objcell_id&0xFFFF>=0x100) / 100.0 outdoor; 315885-315905 GetStartConstraintDistance returns player 5.0 indoor / 10.0 outdoor — both exactly as the finder quoted, and both landblock/indoor-aware via the 0x100 cell-id test. The decomp call sites confirm these govern force-position handling: 144717-144719 gates the viewer/viewpoint \"blip\" update on autonomy distance vs distance_moved, and 145199-145227 feed start/max constraint distances into ConstrainTo/InterpolateTo on the teleport/force-position path. ACE (PhysicsObj.cs:545-623) mirrors the same constants, confirming the server we reconcile against is the same machinery. I also verified the \"JS-side only\" qualifier: the Rust/wasm client only tracks force_position_sequence as ordering bookkeeping (system.rs:33-90) with no distance-based snap/interpolate, and the 2D index.html path snaps only on a landblock crossing (index.html:5960-5964) with no distance threshold at all. So our 5 m flat snap is genuinely far tighter than retail's distances and is not landblock/indoor-aware. Divergence classification is correct.",
              "corrections": "Minor imprecision, not enough to downgrade: retail's autonomy-blip and start/max-constraint distances are not literally one unified \"snap-vs-interpolate threshold.\" The autonomy blip (144717) decides whether to refresh the viewer/cell-visibility (\"blip\") and gates InterpolationManager::InterpolateTo (389055); the start/max constraint distances (145199-145227) define a ConstrainTo leash region; InterpolationManager additionally uses a 0.05 m dead-band (389057). They are the retail force-position correction machinery and are indoor/outdoor-aware, which is the load-bearing part of the claim, but mapping our single 5 m snap directly onto \"the\" retail threshold is a simplification. Also note ACE inverts the start-constraint indoor/outdoor mapping relative to the decomp comment framing (ACE returns 5.0 for <0x100 i.e. outdoor, 10.0 indoor; decomp returns 5.0 for >=0x100 i.e. indoor) — but the finder cited the decomp values (5 indoor / 10 outdoor), which match the decomp text exactly.",
              "checked": "apps/holtburger-web/scene3d/camera.js:904; apps/holtburger-web/scene3d/camera.js:378; apps/holtburger-web/scene3d/camera.js:921; apps/holtburger-web/index.html:5960; crates/holtburger-core/src/client/movement/system.rs:52; ac-headers/acclient.c:315861; ac-headers/acclient.c:315885; ac-headers/acclient.c:144717; ac-headers/acclient.c:145199; ac-headers/acclient.c:389055; ac-headers/acclient.c:389514; external/ACE/Source/ACE.Server/Physics/PhysicsObj.cs:545"
            }
          },
          {
            "id": "heartbeat-unconditional-no-changed-gate",
            "statement": "Our 1 s heartbeat fires unconditionally whenever a sync target exists, re-sending the (possibly un-corrected, drifted) runtime pose; retail only sends a position event when the position actually changed since last_sent_position (Frame::is_equal / contact-plane gate), so a stationary or force-corrected-but-not-locally-applied player keeps re-asserting a stale pose to ACE in our model.",
            "kind": "risk",
            "ours": {
              "file": "crates/holtburger-core/src/client/movement/system.rs",
              "lines": "1407-1453",
              "snippet": "let Some(next_heartbeat_at) = self.next_autonomous_position_heartbeat_at else {...}; if now < next_heartbeat_at { return Ok(false); } ... let Some(pulse) = build_autonomous_position(world, metadata) ...; session.send_action(GameAction::AutonomousPosition(Box::new(pulse))).await?;  // no Frame::is_equal gate"
            },
            "retail": {
              "file": "ac-headers/acclient.c",
              "lines": "718121-718132",
              "snippet": "if (time_between_position_events + last_sent_position_time < cur_time) { result = last_sent_position.objcell_id != m_position.objcell_id || !Frame::is_equal(&last_sent_position.frame, &m_position.frame); } else { ... last_sent_contact_plane == contact_plane ... }"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "server-reconciliation-heartbeat-stale-pose",
              "verdict": "confirmed",
              "reasoning": "Every load-bearing element checks out against the source as it exists today.\n\nOURS: maybe_send_autonomous_position_heartbeat (system.rs:1407-1453) fires on a pure time gate (now >= next_heartbeat_at). The only existence check is has_autonomous_position_sync_target (common.rs:112-118), which tests solely that player.guid != NULL and position.landblock_id != NULL — it does NOT compare the current pose to any previously-sent pose. build_autonomous_position (common.rs:120-137) constructs the pulse directly from world.local_player_runtime_pose() with zero change/equality comparison. The MovementSystem struct (system.rs:123-142) has no last_sent_position / last_sent_pose / dirty / changed field at all; the only `distance` uses (lines 541, 1170) are unrelated autonomous-drive stepping and projection convergence, not a send gate. The constant AUTONOMOUS_POSITION_HEARTBEAT_INTERVAL = Duration::from_secs(1) (common.rs:22) confirms the stated 1 s cadence. So once a sync target exists, the heartbeat unconditionally re-sends the runtime pose every 1 s. This matches the finder's risk description exactly.\n\nRETAIL: ShouldSendPositionEvent (acclient.c:718108-718139) is correctly cited and current. In the timer-elapsed branch (718121-718125) it returns true ONLY if objcell_id changed OR !Frame::is_equal(last_sent_position.frame, m_position.frame); in the not-yet-elapsed branch (718128-718131) only on objcell change or contact-plane inequality. SendPositionEvent (718202-718251) and SendMovementEvent (718187-718191) write back last_sent_position / last_sent_position_time / last_sent_contact_plane after sending, feeding that gate. So retail genuinely suppresses a position event for a stationary pose, while ours does not — the divergence and the \"stale-pose re-assertion\" risk are real and correctly classified as a risk/gap.\n\nLine refs, the 1 s constant, the Frame::is_equal/contact-plane gate, and the absence of any equivalent gate on our side are all accurate. Nothing in the surrounding context (e.g. a hidden clamp, fallback, or dirty-flag) reverses the conclusion.",
              "corrections": "Minor, non-conclusion-changing nuances the finder did not state: (1) Our heartbeat does snap pose Z to the cached terrain heightmap before write-back (documented comment at system.rs:1419-1435), so the re-sent pose is not arbitrary garbage on Z — but it is still the local runtime pose, which can be un-corrected/drifted relative to a server force-correction the client hasn't applied, so the \"stale pose\" risk stands. (2) Retail's gate has two sub-branches (timer-elapsed full Frame::is_equal check vs. not-yet-elapsed contact-plane-only check); the finder's one-line summary collapses these but does not misstate them. (3) The retail caller wiring ShouldSendPositionEvent -> SendPositionEvent is dispatched via vtable in the decomp so no direct textual call site is greppable; this does not affect the gate semantics, which are self-evident in the function bodies.",
              "checked": "OURS: crates/holtburger-core/src/client/movement/system.rs:1400-1475, 123-142, 248-263, 511-518; crates/holtburger-core/src/client/movement/common.rs:112-137, 22. RETAIL: ac-headers/acclient.c:718108-718139 (ShouldSendPositionEvent), 718142-718198 (SendMovementEvent), 718202-718251 (SendPositionEvent)"
            }
          },
          {
            "id": "local-vs-remote-reposition-asymmetry",
            "statement": "Remote entities get a hard authoritative reset on force-position (EntityPositionSyncOutcome::Reset → SpatialBodyEvent::ForcedReposition snaps body.pose), but the local player's force-position only updates authoritative_pose and emits a ForcedReposition WorldEvent without snapping the integrator's working pose, so the wasm authoritative state is never corrected by a sub-snap rubberband — convergence depends solely on the JS layer.",
            "kind": "risk",
            "ours": {
              "file": "crates/holtburger-world/src/player/mutations.rs",
              "lines": "192-219",
              "snippet": "fn update_position_from_server(...) { ... self.force_position_sequence = pos_pack.force_position_sequence; ... if is_newer_u16(self.force_position_sequence, old_forced_seq) { events.push(WorldEvent::ForcedReposition { guid, pos: pos_pack.pos, sequence: ... }); } }  // set_player_position then uses Snapshot mode (preserve_local_runtime_pose)"
            },
            "retail": {
              "file": "ac-headers/acclient.c",
              "lines": "144748-144765, 145242-145248",
              "snippet": "BlipPlayer: SetPositionSimple(player, new_pos, 1); ... else { Frame::set_heading; SmartBox::BlipPlayer(v10, &pos); player->update_times[0] = position_timestamp; }  // force-position path hard-snaps player in retail"
            },
            "confidence": "medium",
            "impact": "high",
            "verdict": {
              "claim_id": "server-reconciliation",
              "verdict": "confirmed",
              "reasoning": "The core asymmetry the claim describes is real and current. REMOTE entities: apply_entity_position_pack / apply_entity_autonomous_position (state/mutations.rs:491,536) call Entity::apply_server_position_update, which returns EntityPositionSyncOutcome::Reset whenever reset_required (newer force/teleport seq or position_seq absent — entity.rs:393-400). emit_entity_position_sync's Reset arm (state/mutations.rs:318-340) calls reconcile_authoritative_body(..., AuthoritativeBodySync::Reset), and in scene.rs:1220-1248 a Reset forces preserve_local_runtime_pose=false (the preserve guard at 1230-1235 requires Snapshot), so body.pose is hard-snapped to the server pose (line 1244). LOCAL player: every force-position route lands on set_player_position → update_player_position(AuthoritativeBodySync::Snapshot) (state/mutations.rs:606). For UpdatePosition the wasm runs apply_position_from_server → update_position_from_server (player/mutations.rs:192-219, the finder's cite), which only bumps force_position_sequence and pushes WorldEvent::ForcedReposition — it never touches the scene body — then handlers/player.rs:42 calls set_player_position with Snapshot. For AutonomousPosition, apply_player_autonomous_position (state/mutations.rs:700) likewise calls set_player_position (Snapshot). With LocalPlayer + Snapshot + a Simulating* mode (the normal moving state), preserve_local_runtime_pose=true, so scene.rs:1243-1246 SKIPS body.pose=pose and only sets authoritative_pose (1237). I confirmed there is NO Reset path for the local player: EntityPositionSyncOutcome::Reset is reached only via the entity-sync arm (remote), and the only production caller of apply_forced_reposition_reset is the SpatialBodyEvent::ForcedReposition handler, whose sole emitter (simulation.rs:399) is a test using SpatialBodyId::Entity. So the wasm authoritative working pose for the local player is never sub-snap rubberbanded; convergence depends on the JS layer consuming the ForcedReposition WorldEvent. Retail diverges: the position-event handler (acclient.c ~145150-145249) drives the local player's real physics object directly — ConstrainTo+InterpolateTo on the live path (145201-145218) and SmartBox::BlipPlayer→SetPositionSimple hard snap on the stale-force-position else branch (145242-145248, BlipPlayer at 144748-144765). The risk classification (wasm authoritative state is not corrected by a sub-snap; convergence offloaded to JS) is accurate, and matches an explicitly-named test in the tree (simulation_tick_advances_local_player_runtime_body_without_mutating_authoritative_pose, client/mod.rs:1259).",
              "corrections": "One mechanistic imprecision (does not change the conclusion): the remote-entity body.pose snap happens via reconcile_authoritative_body(AuthoritativeBodySync::Reset) at scene.rs:1244, NOT via SpatialBodyEvent::ForcedReposition. SpatialBodyEvent::ForcedReposition is a SEPARATE channel handled at state/mutations.rs:254 (calls apply_forced_reposition_reset, also snapping body.pose) and in production has no emitter today — its only emitter (simulation.rs:399) is a test, and even there it targets a remote Entity. So the finder conflated two distinct remote-snap mechanisms under one arrow; the operative remote path is the EntityPositionSyncOutcome::Reset → reconcile(Reset) one. Also a nuance on the retail side worth flagging: retail's local-player correction is usually a SOFT ConstrainTo/InterpolateTo (server pulls you back over time), with the hard SetPositionSimple Blip reserved for the stale/equal-force-position else branch — so 'hard authoritative reset' overstates the common retail local-player case, though retail does still apply the correction to the live physics object whereas ours does not touch the wasm working pose at all.",
              "checked": "crates/holtburger-world/src/player/mutations.rs:192-219; crates/holtburger-world/src/spatial/scene.rs:1211-1249,1385-1395; crates/holtburger-world/src/state/mutations.rs:318-340,479-547,604-708; crates/holtburger-world/src/entity.rs:365-404; crates/holtburger-world/src/handlers/player.rs:33-47; crates/holtburger-world/src/handlers/movement.rs:32-55; crates/holtburger-core/src/client/simulation.rs:399; /home/wbterminal/ac-headers/acclient.c:144748-144765,145150-145249"
            }
          },
          {
            "id": "teleport-suspends-bodies-match",
            "statement": "Our PlayerTeleport handling (advance teleport_sequence, suspend runtime bodies, emit TeleportStarted) mirrors retail's teleport path which zeroes velocity and hard-SetPositionSimple via TeleportPlayer, and our JS predictor's >5 m snap covers the post-teleport pose jump.",
            "kind": "match",
            "ours": {
              "file": "crates/holtburger-world/src/handlers/player.rs",
              "lines": "71-78",
              "snippet": "GameMessage::PlayerTeleport(data) => { state.player.set_teleport_sequence(data.teleport_sequence); events.extend(state.suspend_runtime_bodies(RuntimeBodyResetCause::TeleportOrWorldReset)); events.push(WorldEvent::TeleportStarted { sequence: data.teleport_sequence }); true }"
            },
            "retail": {
              "file": "ac-headers/acclient.c",
              "lines": "144733-144744, 145196-145206",
              "snippet": "TeleportPlayer: SetPositionSimple(v4, new_pos, 1); PlayerPositionUpdated(v3, a2, 1, 3.4e38); ... if (newer_event(TELEPORT_TS,...)) { TeleportPlayer; ConstrainTo; set_velocity(v24, &zero, 1); }"
            },
            "confidence": "high",
            "impact": "medium",
            "verdict": {
              "claim_id": "server-reconciliation-teleport",
              "verdict": "partially-correct",
              "reasoning": "The structural match is real and well-grounded. OURS (handlers/player.rs:71-78) advances teleport_sequence, suspends runtime bodies (scene.rs:1397-1405 resets each body to authoritative pose + halts sampling — analogous to halting velocity), and emits TeleportStarted — verified verbatim. RETAIL TeleportPlayer (acclient.c:144733-144744) does SetPositionSimple(new_pos,1)+PlayerPositionUpdated, and the caller HandleReceivedPosition zeroes velocity via set_velocity(0,0,0) at 145203-145206, gated by newer_event(TELEPORT_TS) at 145196 — verified verbatim. Our teleport_sequence gate (should_accept_server_position_sequences, mutations.rs:226-242) mirrors retail's TELEPORT_TS newer_event check, and the position is applied on the subsequent UpdatePosition in both systems (our own comment at lib.rs:27651-27666 documents this: \"runtime pose will snap on next UpdatePosition\"). HOWEVER one load-bearing detail is fabricated: the claimed \">5 m snap\" in the JS predictor does not exist. The local-player teleport snap (index.html:5960-5968) keys off landblock-ID crossing (high 16 bits of landblockId), and wasm reconciliation is sequence-based (teleport/force_position via is_newer_u16). A repo-wide search found zero 5-meter distance thresholds. So the match conclusion holds but the cited mechanism for the JS snap is wrong.",
              "corrections": "(1) The \">5 m snap\" in the JS predictor is fabricated — there is no 5-meter distance threshold anywhere in the repo. The actual JS local-player teleport snap (index.html:5960-5968) triggers on landblock-ID crossing (high 16 bits of upd.landblockId != entry.lastLocalLbId), and the wasm-side reconciliation is purely sequence-gated (teleport_sequence / force_position_sequence, is_newer_u16), not distance-gated. (2) Minor: TeleportPlayer itself does NOT zero velocity — it only does SetPositionSimple + PlayerPositionUpdated; the velocity-zero (set_velocity 0,0,0) lives in the caller SmartBox::HandleReceivedPosition at 145203-145206 (the finder's cited range, so substance is fine). (3) The finder omitted retail's ConstrainTo call (145199-145201) which is part of the teleport path, though not central to the match.",
              "checked": "crates/holtburger-world/src/handlers/player.rs:71-78; crates/holtburger-world/src/spatial/scene.rs:1397-1405; crates/holtburger-world/src/player/mutations.rs:226-260; apps/holtburger-web/index.html:5917-5990; apps/holtburger-web/src/lib.rs:27640-27684; /home/wbterminal/ac-headers/acclient.c:144733-144766,145124-145228"
            }
          }
        ],
        "open_questions": [
          "Does the wasm authoritative pose ever get corrected after a sub-5 m force-position rubberband? The Snapshot-mode preserve_local_runtime_pose path means body.pose is untouched and the next heartbeat re-sends the drifted pose; I could not find any code path that snaps the local integrator's working pose back to a sub-snap forced position, so persistent small-drift rubberbands may oscillate. Needs a live capture to confirm.",
          "Retail's ConstrainTo/InterpolateTo go through PositionManager which I did not fully read (acclient.c:388317 InterpolateTo / 388367 ConstrainTo) — the exact blend rate / constraint spring behavior is unquantified, so the claim that our 150 ms JS lerp is 'retail-plausible' is directionally right but not numerically validated against PositionManager's actual smoothing.",
          "The ACE checkout here is partial (Physics + WorldObjects only; no Network/GameAction handlers), so I confirmed ACE's server-side UpdatePlayerPosition/SendUpdatePosition/ObjectForcePosition cadence but could NOT read ACE's inbound AutonomousPosition message handler that calls SetRequestedLocation — the client→server RequestedLocation plumbing was inferred from SetRequestedLocation's doc-comment, not observed at its call site.",
          "Whether our JS predictor reliably sees local-player force-positions: loop.js writes __lastEntityWorldPos for ALL guids (the isLocalPlayerGuid check only gates em.setPose), but I did not trace that an inbound UpdatePosition for the local player actually produces a KIND_POSITION drain event (vs being suppressed like KIND_SPAWN) — if it is suppressed, the JS 5 m snap would never fire for the local player and rubberbands would never correct visually.",
          "Retail's ShouldSendPositionEvent has a second branch (when within the 1 s window) that still sends if the contact_plane changed — our heartbeat has no contact-plane-change trigger, only the fixed interval; unclear whether this matters for landing/contact-transition fidelity."
        ]
      }
    ],
    "tally": {
      "confirmed": 44,
      "partially-correct": 13
    }
  }
}