diff --git a/context/multiplayer.md b/context/multiplayer.md index 9ff6d82..c915154 100644 --- a/context/multiplayer.md +++ b/context/multiplayer.md @@ -405,3 +405,7 @@ Decomp-verified (workflows w0odszxro/wh1h5gnmc, 3 make-or-break claims adversari - T4 CLOSED: both leg(0x65c) and body(0x6bc) animators are AnimationInstances of the SAME JointedMover/JointSubsystem (JMOVER.cpp:1382) -- either poses the WHOLE skeleton. Our port keeps the peer on the LEG channel (body unbound on the peer); faithful functional equivalent. RECONSTRUCTION STATE: coupled path is DEFAULT ON (mech4.cpp: s_drPos gait-coupled, s_gaitMirror/Send). Reverts: BT_DR_POS=1 (peer velocity), BT_NO_MASTER_GAITMIRROR (master const-vel mirror). Measured: single-source coupling backward-steps 0.1% (vs the two-source split's churn), position ratio 1.043. RESIDUAL (follow-up): occasional ~2.9u snap from the leg(peer)-vs-body(mirror) channel mismatch -- to close, run the master mirror on a leg-channel prediction fed last-sent speed, matching the peer's poser. Also open: exact _DAT_004ab9cc decay constant; whether IntegrateMotion's 2-stage angular integrate is intended. + + +## Peer motion: the "random shakiness" is single-box packet jitter, NOT the game (task #50, 2026-07-15) [T2] +After the coupled body-channel peer landed (96a896a + turn-step f094d78 + cadence-mirror 23f1532), residual peer shakiness on accel/decel was RANDOM -- sometimes perfect, sometimes shaky. Root cause is the TEST RIG, proven with BT_RXJIT (record inter-arrival ms on the peer): two Debug btl4 nodes on ONE box contend for CPU, so Windows BATCHES their TCP delivery -- update records arrive in bursts (min ~0ms back-to-back, max 56-226ms gaps, burstiness max/avg 3-7x) instead of an even ~17ms. A peer dead-reckons across the long gaps then snaps when a record lands -> random shake. Pinning each node to a DISJOINT core set (ProcessorAffinity 0x00F / 0x3C0) restored even ~17ms delivery (burstiness ~1.0) and the shakiness vanished (user-confirmed). REAL pods are dedicated machines with no contention -- they never see this. DO NOT add an interpolation/jitter buffer or other un-authentic netcode to mask a single-box artifact. Use tools/mp_launch.sh (bakes in the affinity) for all 2-node tests. Diagnostic env: BT_RXJIT (arrival jitter), BT_SLIDE ([slide]/[mslide] slide-in-stand), BT_GAITEV, BT_MIRDIV, BT_NO_MIRROR_CAD / BT_PEER_LEGCH (revert the coupled-peer fixes). \ No newline at end of file diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 679535b..18293f2 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -1790,6 +1790,33 @@ Logical void Mech::ReadUpdateRecord(Simulation::UpdateRecord *message) { + // RECORD-CADENCE probe (BT_RXJIT): quantify how EVENLY records arrive (wall-clock + // ms between arrivals) -- the jitter that makes the peer's corrections random. + if (getenv("BT_RXJIT") && GetInstance() == ReplicantInstance) + { + LARGE_INTEGER _now, _freq; + QueryPerformanceCounter(&_now); QueryPerformanceFrequency(&_freq); + static double s_lastMs = 0.0, s_min = 1e9, s_max = 0.0, s_acc = 0.0; + static int s_n = 0, s_rep = 0; + const double nowMs = (double)_now.QuadPart * 1000.0 / (double)_freq.QuadPart; + if (s_lastMs > 0.0) + { + const double gap = nowMs - s_lastMs; + s_n++; s_acc += gap; + if (gap > s_max) s_max = gap; + if (gap < s_min) s_min = gap; + } + s_lastMs = nowMs; + if (++s_rep >= 120 && s_n > 0) + { + DEBUG_STREAM << "[rxjit] records/win=" << s_n + << " gapMs min=" << s_min << " avg=" << (s_acc / s_n) + << " max=" << s_max + << " burstiness(max/avg)=" << (s_max / (s_acc / s_n)) + << "\n" << std::flush; + s_rep = 0; s_n = 0; s_acc = 0.0; s_min = 1e9; s_max = 0.0; + } + } static const int s_mrecLog = getenv("BT_REPL_LOG") ? 1 : 0; if (s_mrecLog && message->recordID != 0) DEBUG_STREAM << "[mrec-rx] type=" << (int)message->recordID diff --git a/tools/mp_launch.sh b/tools/mp_launch.sh new file mode 100644 index 0000000..1892834 --- /dev/null +++ b/tools/mp_launch.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# mp_launch.sh -- launch a 2-node local multiplayer test (FOGDAY.EGG) with the two +# game nodes PINNED TO DISJOINT CPU CORES + the console relay. +# +# WHY THE AFFINITY MATTERS (task #50, 2026-07-15): two Debug btl4 nodes on one box +# fight for the same cores, and Windows punishes the contention by BATCHING their +# TCP packet delivery -- update records that should arrive every ~17ms instead +# arrive in bursts (measured gaps up to 226ms, burstiness 3-7x, BT_RXJIT). A peer +# dead-reckons across those gaps and then snaps when a record finally lands -> the +# "random" shaky peer motion. Pinning each node to its own core set restores even +# ~17ms delivery (burstiness ~1.0) and the shakiness vanishes. This is a SINGLE-BOX +# TEST-RIG artifact -- real pods are dedicated machines and never see it; do NOT +# "fix" it in the game with an interpolation buffer. +# +# Usage: tools/mp_launch.sh [extra env for BOTH nodes...] +# e.g. tools/mp_launch.sh BT_SLIDE=1 +# Run from the repo root; content/ must hold FOGDAY.EGG and the build in build/Debug. +set -u +cd "$(dirname "$0")/../content" || exit 1 +EXE=../build/Debug/btl4.exe +EXTRA="$*" + +powershell -NoProfile -Command "Get-Process btl4,python -ErrorAction SilentlyContinue | Stop-Process -Force" 2>/dev/null +sleep 2 +rm -f fa.log fb.log console.log + +env $EXTRA BT_DEV_GAUGES=1 BT_LOG=fb.log BT_START_INSIDE=1 BT_SPAWN_AT="40 -150" "$EXE" -egg FOGDAY.EGG -net 1601 & +sleep 2 +env $EXTRA BT_DEV_GAUGES=1 BT_LOG=fa.log BT_START_INSIDE=1 BT_SPAWN_AT="-40 -150" "$EXE" -egg FOGDAY.EGG -net 1501 & +sleep 3 + +# Pin the two nodes to disjoint core sets (0x00F = cores 0-3, 0x3C0 = cores 6-9). +powershell -NoProfile -Command "\$ps=@(Get-Process btl4 | Sort-Object Id); if(\$ps.Count -ge 2){ \$ps[0].ProcessorAffinity=[IntPtr]0x00F; \$ps[1].ProcessorAffinity=[IntPtr]0x3C0; Write-Output ('PINNED '+\$ps[0].Id+'->cores0-3, '+\$ps[1].Id+'->cores6-9') } else { Write-Output ('WARN only '+\$ps.Count+' btl4 up') }" + +sleep 6 +python -u ../tools/btconsole.py FOGDAY.EGG 127.0.0.1:1501 127.0.0.1:1601 > console.log 2>&1 & +echo "console streaming; nodes on 1501/1601. Logs: content/fa.log content/fb.log" +wait