Files
BT411/context/multiplayer.md
T
arcattackandClaude Fable 5 5e993de99d Operator-set callsigns: the console's name-bitmap generator recreated
The 1995 console let the operator type player callsigns; the pods render
them from 1bpp BITMAPS streamed in the egg ([largebitmap]/[smallbitmap]
lists + hex raster pages, bitmapindex per pilot; large 128x32 on the
score/ranking display via DPLRenderer name textures, small 64x16 on the
kill-feed and radar/target labels). The textual name= pilot key is dead
in the shipped binary -- its pilot parse reads bitmapindex only (decomp
part_014). Historical bug surfaced by this work: every egg in the repo
listed only ONE name bitmap (Aeolus) while pilot 2 pointed at index 2,
so player 2+'s callsign has never rendered (null-checked, silently
blank).

eggmodel.py: EggDoc.set_callsigns rewrites the lists + one raster page
per callsign per size + per-pilot name=/bitmapindex= (duplicate names
get disambiguated page tags); get_callsign_rows decodes pages back
(exact inverse of the engine's nibble-stream parse, GRAPH2D.cpp:340,
MSB-first bit order :558); rasterize_callsign/make_callsigns render
text via Qt (auto-shrink to fit, NoAntialias, 1bpp threshold) -- the
only PySide6-dependent corner of the module. validate() now catches
dangling bitmapindexes and missing raster pages.

btoperator.py: Callsign column in the roster (free text, PLAYERn
default); every save rasterizes the column into the egg exactly like
the original console did.

MP_BHMC.EGG template repaired: authentic Aeolus raster preserved
bit-exact, Boreas page generated and listed.

Verified: eggmodel self-test 20/20 (bit-exact raster round-trip, dup
disambiguation, dangling-index catch); ASCII-art decode of the
app-generated VIPER/MONGOOSE rasters reads correctly; live 2-node
relay session on the generated egg -- full ladder, launch, zero
name-bitmap load warnings; operator GUI e2e PASS after the column
shift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 12:29:57 -05:00

47 KiB
Raw Blame History

id, title, status, source_sections, related_topics, key_terms, open_questions
id title status source_sections related_topics key_terms open_questions
multiplayer Multiplayer — replication, netcode, the console provisional PROGRESS_LOG.md §7 (Phase 7), §8 (P6); docs/HARD_PROBLEMS.md (P6)
wintesla-port
combat-damage
locomotion
decomp-reference
master
replicant
EGG
dead-reckon
pod-LAN config (real IPs); peer warp DropZoneLocation-anchoring [T3] (world-anchored peer sphere already visible, 160b78e); console-death replication freeze (pending recurrence to diagnose)

Multiplayer

Pod-to-pod networked play. The hard infrastructure exists (WinTesla replaced DOS NETNUB with a WinSock2 TCP stack), and MP is now substantially reconstructed + 2-node verified — replication, cross-pod combat, beam visuals, replicant gait, respawn (tasks #46-#52). Remaining: pod-LAN config, fidelity nuances, and the Mech-level update records. Full detail: docs/PROGRESS_LOG.md §7, §8; the problem writeup: docs/HARD_PROBLEMS.md (P6).

The stack (already reconstructed)

  • WinTesla replaced NETNUB with a ~3.5k-line WinSock2 TCP reimplementation (MUNGA_L4/L4NET.CPP, SOCK_STREAM/ReliableMode, zero SOCK_DGRAM); the master/replicant distributed-sim core (InterestManager/Entity/HostManager) is complete. MP was integrated on that stack and verified end-to-end 2-node. The rumored L4NET.CPP:1853 send-size bug is DEAD CODE (#if MESSAGE_BUFFERING==0). [T2]

Master / replicant model

  • master = a locally-simulated entity (your mech + dummies); drives itself + EMITS update records.
  • replicant = a peer's mech, a local proxy; DeadReckons toward the master's replicated position. MUST SetValidFlag() at creation or every message defers forever (reconstruction-gotchas §9).

How a session forms

-net <port> = networked mode (the pod listens on for a CONSOLE, boots ConsoleOnly; game mesh = +1). Solo (no -net) never touches WinSock. The mission egg's [pilots] section IS the roster (pilot=ip[:port] + a per-address page); each pod self-identifies by local-IP+game-port, connects to earlier pilots, listens for later ones — a deterministic full mesh; the mission loads when all connect. The CONSOLE (operator station — ABSENT from every archive) delivers the egg as chunked ReceiveEggFileMessage packets + the LAUNCH RunMissionMessage; tools/btconsole.py is our console emulator (⚠ NotationFile::ReadText expects NUL-SEPARATED lines). [T2]

Verified milestones (one box, two instances)

  • -net node render glitch — LIKELY ALREADY FIXED / WATCHING (task #53): a 2-node MP session once showed ONE window come up visually wrong — shadow clipping, camera stuck inside/blocked, targeting/firing dead — while the other was fine. Two earlier framings were WRONG and are corrected: (i) the "two-D3D9-apps-on-one-GPU / device contention" theory was retracted — a solo instance, AND two solo instances, are all clean, so it is not device contention; (ii) "glitched FROM LOAD, before any death" was ALSO wrong — the trigger is a RESPAWN (user correction, 2026-07-10). So it is a post-respawn render-rebuild fault on the MP path, not a load-time one. Does NOT affect the pod (one instance per machine).
    • What the respawn rebuild does (reproduced 2026-07-10): on respawn the player's mech is rebuilt via MakeEntityRenderables, which runs a TWO-STEP view sequence — inside skeleton + COCKPIT eyepoint, then outside skeleton + external chase ([view] … logs, btl4vid.cpp:2158) — and the external chase camera is NOT re-installed (only ONE [BTrender] external debug chase camera installed, btl4vid.cpp:647, for the whole session despite multiple respawns). Plausible failure: if that two-step completes only to the COCKPIT step (raced/interrupted on the -net ladder), the node is left stuck INSIDE — body hidden by the inside skeleton, shadow/reticle flagging with it = the reported bundle. Camera + reticle + view + tshd shadow are all gated on buildDebugChaseCamera = (view_type == insideEntity) (btl4vid.cpp:297).
    • Status: could NOT reproduce (2026-07-10) → likely already fixed. 9 automated 2-node loads PLUS a forced death/respawn all came up CLEAN (the respawn completed to external chase; clean backbuffer via the new BT_SHOT dump). Since debug uninit is a deterministic 0xCDCDCDCD (would glitch every run), the intermittency implies a race, and the task #52 respawn fixes (the LoadMission re-entry killer bug, viewpoint relink, death-latch) plausibly ALREADY closed it. Left as WATCH — re-open and capture with BT_SHOT if it recurs. Tools kept: BT_SHOT=<path> (per-instance backbuffer PNG, off by default, L4VIDEO.cpp) + scratchpad/hunt53.sh (N-run divergence hunter).
  • P6 smoke test: two instances share a world — console egg → TCP mesh → synchronized start → bidirectional entity replication (each renders its own mech + the peer's REPLICANT), 0 crashes.
  • Movement replication: A's mech WALKS on B's screen (the replicant tracks the master to ~1.5u via dead-reckoning). The authentic mission-start ladder runs (CreatingMission→…→RunningMission via the console LAUNCH). SIX bugs fixed to get here (dead-reckoner install, replicant-motion DeadReckon, master emission threshold, emission gated on RunningMission, the console-must-LAUNCH fact, replicant validity). [T2]
  • Wire-format bug class found+fixed: MakeMessages replicate RAW over TCP, so string payload must be INLINE (char[N] at the binary offsets), not a const char* pointer (garbage cross-pod). Check EVERY MakeMessage for pointer payloads. [T2]

Debug tooling (BT_NET_TRACE, permanent)

[net-tx]/[net-rx] (L4NET), [net-upd] (update lookup), [upd-repl], [ent-exec] (state ladder). Per-instance: BT_LOG=<file> + BT_AFFINITY=<mask> (CPU pin). Update stream ≈ 60 Hz × 144-byte records per moving master. [T2]

Cross-pod combat step 1+2 (task #46, 2026-07-09) [T2 findings]

Step 1 — target ANY peer mech: DONE + verified. A live-mech registry (BTRegisterMech/ BTDeregisterMech from the Mech ctor/dtor, btplayer.cpp) collects every Mech — player, dummy, AND peer replicants. The mech4 boresight world-pick now walks BTGetTargetCandidates (all mechs ≠ shooter) and picks the CLOSEST the ray strikes, retargeting the whole fire/damage block from the solo gEnemyMech to the picked hotTarget (missile impact + projectile validation generalised via BTIsRegisteredMech). Verified one-box: instance A correctly enumerates B's mech as a live ReplicantInstance (inst=4), 20 zones, ownerID=3, alive. Solo un-regressed (pick→damage→kill chain clean). Step 2 — cross-pod damage: SOLVED + verified end-to-end (task #47, 2026-07-09). A one-line fix (Mech::Make) yields a full cross-pod KILL: instance A (host 2) shoots B's mech (host 3) dead over the network, B runs the death transition (wreck swap + explosion) on its own screen. [T2]

THE BUG (root cause): B's own MASTER mech was INVALID, so every dispatched network message to it was DEFERRED forever. Entity::Receive(Event*) (ENTITY.cpp:165) does if(!IsValid()) event->Defer(); return;. The reconstructed Mech::Make (mech.cpp) force-set ValidFlag ONLY for replicants (the P6 bring-up hack for the partial MakeReady/CheckLoad handshake); a locally-created master never got validated. So A→B TakeDamage arrived, resolved to B's real master mech, Posted, drained to Entity::Receive — which saw valid=0 and Deferred it. Handler never ran → 0 damage.

THE FIX: Mech::Make now sets ValidFlag for the master too (if (mech != 0) mech->SetValidFlag(); — the reconstructed ctor builds the whole mech synchronously, so it's safe). NOTHING ELSE changed: the targeting, the virtual victim->Dispatch, the reroute, the wire, the receive, and the id resolution were all already correct. Solo un-regressed (the solo enemy was already force-valid via btplayer:839; the change only ADDS ValidFlag).

Verified full path (2-node one-box, BT_MP_FORCE_DMG on A firing at B's replicant once/sec):

  1. A: victim->Dispatch(&td) (VIRTUAL — the real beam path) → Entity::Dispatch reads inst=Replicant, stamps entityID=3:22 (B's master id), if(ReplicantInstance) SendMessage(ownerID=3, EntityManagerClientID).
  2. Wire: NetworkManager::Send (L4NET.CPP:1062) → send() to host 3 (online).
  3. B: ReceiveNetworkPacket (NTTMGR.cpp:107) → GetEntityPointer(3:22) = entityIndexSocket.Find (HOSTMGR.h:270) → resolves to B's master mech (classID 3001=0xBB9)Post(EntityManagerEventPriority).
  4. B: event drains → Entity::Receive (now valid=1) → Mech::TakeDamageMessageHandler (mech.cpp:584) → invalidZone (1) cylinder lookup → zone damage. ~36 hits × 12 → B's mech DESTROYED + death transition.

Two prior mis-diagnoses corrected — both were the WRONG messageID. Entity::TakeDamageMessageID is 18, NOT 21 (confirmed at runtime: td.msgID=18, so Simulation::NextMessageID=3, not 6). Every earlier probe filtered msgID==21, so it NEVER matched a real TakeDamage — the stray msgID=21 hits it caught were an unrelated message, and correlating their 3:19 / classID 48 against the real 3:22 TakeDamage produced two phantom root causes: (a) the "cross-TU Entity layout divergence" (disproven separately — an offsetof probe showed Entity is IDENTICAL in both TUs: sizeof=444, entityID@380 (0x17C), ownerID@388, simulationFlags@32; distinct from the REAL P5 raw-offset stomps at this+0x2d4..0x2f0 in docs/HARD_PROBLEMS.md), and (b) the "localID drops by hostID on the wire" claim. Both are FALSE. The id is correct end-to-end (3:22 = B's master); nothing on the wire is corrupted. Lesson: when a message probe seems to see a wrong id, first confirm the numeric messageID.

Retained gated instrument: [mp-force] (BT_MP_FORCE_DMG + BT_MP_LOG) — the 2-node cross-pod reproducer, off in solo. [mp-hdlr] (BT_MP_NET, mech.cpp:591) logs each applied cross-pod hit. 2-node launch recipe + scratchpad/mp_test.sh: A -egg MP.EGG -net 1501, B -net 1601, then python tools/btconsole.py MP.EGG 127.0.0.1:1501 127.0.0.1:1601 (MP.EGG [pilots] = game ports 1502/1602).

Interactive -net aim + drive (task #48, 2026-07-09) [T2]

Re-measured on a 2-node one-box run. The prior "aim ray dead / drive dead in -net" claim was STALE — both work: A's aim ray is live (noRay=0) and A drives (BT_AUTODRIVE/BT_GOTO move it).

FIXED — torso-pitch aim ray (gyro-stabilized boresight). The aim/pick ray was the active RENDER EYE's view direction, which (a) inherits the mech BODY pitch — on a slope the body tilts ~8° to conform to terrain and the boresight pointed that far DOWN, hitting terrain ~50-100 m short of the enemy (groundPicks, never mechPicks); and (b) in CHASE view (the DEFAULT) is the elevated behind-the-shoulder camera, so the ray started over the mech's shoulder. Fix (L4VIDRND DPLEyeRenderable::Execute, the BTSetAimCamera publish): LEVEL the boresight (drop the view-dir pitch, rebuild an upright basis with world +Y) AND anchor its ORIGIN at the mech's torso (myEntity->localToWorld translation + ~5) instead of the render eye. The guns now fire along the mech's horizontal heading from the torso, independent of view mode and terrain tilt. The reticle X still carries the torso twist (BTTwistToReticleX); reticle Y any elevation. VERIFIED end-to-end: solo, enemy 120 m ahead, both chase AND cockpit view → [target] MECH under boresight ... mechPicks=59 groundPicks=0 → beam → *** DESTROYED. Also proved PickRayHit resolves a REPLICANT when aimed at it (hit=1 at 757 m in a 2-node run); the replicant localToWorld is correct (DeadReckon + localToWorld= localOrigin, mech4.cpp:1191). [T2]

FIXED — drive-to-range (the automated cross-pod kill now runs end-to-end). The BT_GOTO beeline had three separate faults: (1) it was gated behind if(gBTDrive.forced) (mech4.cpp), and forced was set ONLY by BT_AUTODRIVE — so BT_GOTO alone never drove (chicken-and-egg: the block that would drive was gated on the drive being on). Fix: BT_GOTO now enables forced mode itself + supplies its own forward throttle (a "beeline" must drive, not just steer), cutting throttle inside a stop radius (weapon range for enemy, so it holds and shoots instead of ramming). (2) The steering used the gDriveHeading SCALAR MIRROR (seeded to 0), but MP pilots spawn facing a non-zero heading, so the beeline steered the WRONG way. Fix: compute the heading error from the mech's ACTUAL forward (localToWorld's -Z axis), signed angle to the target. (3) The goto turn was routed through controlsMapper->turnDemand, which read 0 in -net — the heading froze and A drove straight past B. Fix: apply the goto turn to the orientation integration DIRECTLY (if(gBTGotoActive) turn=gBTGotoTurn), after the mapper read. CAUSE FOUND LATER (task #51): turnDemand zeroed because controlMode was pointer garbage — the pilotArray[1] shrunk-span overrun (see reconstruction-gotchas §5), MP-only because solo's overrun wrote 0==BasicMode. Fixed at the root (10-slot array); the direct goto-turn stays (harmless, and it skips the 1-frame mapper latency). KEYBOARD turning in MP was restored by the root fix. VERIFIED 2-node: A BT_GOTO="enemy" alone (no autodrive) drives dist 1673→231 m, holds at range, mechPicks=59, autofire → B hdlr=192, DESTROYED — a fully automated human-style cross-pod kill via the real beam path (not the FORCE_DMG hook). Solo un-regressed (plain autodrive + goto both work). scratchpad/mp_kill_test.sh; BT_GOTO_LOG traces the beeline.

Replicant gait animation (task #50, 2026-07-09) — DONE [T2]

A peer's replicant used to slide as a frozen statue (DeadReckon moved it; nothing animated it). Now the replicant block (mech4.cpp, after DeadReckon) derives a SIGNED speed demand from the replicated velocity — the master publishes worldLinearVelocity in every update record (Mover::WriteUpdateRecord MOVER.cpp:759), the replicant stores it in updateVelocity.linearMotion (:712); sign = the forward dot against the mech's -Z facing — writes it to controlsMapper->speedDemand (the LEG channel's live source) and advances the LEG state machine for its JOINT writes only. Travel stays DeadReckon (the returned distance is discarded) so position always follows the master; the clip cadence matches the replicated speed. ⚠ REPLICANT GAIT-SCALAR PRIMING (same class as the master's per-frame primes, which live in the PLAYER-only drive block): globalTimeScale/idleStrideScale read 0 on a replicant (clip frozen at advance-time dt*0, legState engaged but legCycle stuck 0), and reverseSpeedMax2@0x7a0 (the run-cycle rise-clamp; LoadLocomotionClips does NOT set it) read debug-heap garbage → legCycleSpeed clamped to -4.3e8 entering run state 13. Both primed in the replicant block; legCycleSpeed itself sane-banded once. VERIFIED 2-node: the observer's replicant runs the full lifecycle — stand→walk(5,7)→run(10,12) with legCycle 7.5→34 tracking the master's speed, wind-down (8) and back to Standing(0) when the master stops. Solo un-regressed. BT_REPL_LOG traces pos/vel/legState/legCycle; recipe scratchpad/mp_gait_test.sh. NOTE: the update-record velocity is currently the master's ACTUAL travel velocity (worldLinearVelocity from the leg channel); the binary publishes the BODY channel's smoothed projection (projectedVelocity, disasm +0x2a0). Swapping the source is a fidelity refinement DEFERRED — it risks a verified working feed for a smoothing nuance. [T3 on that nuance]

Mech-level update records (task #1, 2026-07-11) — DONE [T1 transcription / T2 live]

The 9-type Mech record channel is LIVE end-to-end: writer @004a0c2c transcribed instruction-for-instruction from reference/decomp/mech_writeupdate_004a0c2c.disasm.txt, reader @004a1232 rewritten from part_012.c:9576-9692 with every Wword absorber promoted to the NAMED engine/port member. Wire sizes verified live (0x14/0x20/0x2c/0x78). Key mechanics:

  • Record type == the updateModel BIT INDEX (Simulation::WriteSimulationUpdate walks the mask, SIMULATE.cpp:302-328; binary twin FUN_0041bd98). Request = Mech::ForceUpdate(1<<N) (@0x4a4c54: updateModel |= mask, &= 0xfe03 filter when disabled). THREE prior mislabels of the same binary fns are retired: SetInstanceFlags and RequestActionFlags (both @0x4a4c54 — +0x18 is Simulation::updateModel, NOT entity flags) and IsNetworkCopy (@0x49fb54 = IsDisabled, simulationState 2||9).
  • movementMode == Simulation::simulationState.currentState (binary mech+0x40 = StateIndicator@0x2c +0x14; oldState @+0x3c). The port's parallel int movementMode member is GONE — MovementMode()/SetMovementMode() accessors ride the engine StateIndicator, so the state (death 9 / limbo 2 / airborne 3,4) replicates in EVERY record header (+0xC) and the replicant tracks the master automatically.
  • Every record tail = the mapper's speedDemand (subsystemArray[0]+0x128) mirrored into bodyTargetSpeed@0x6b4 on both sides — the authentic replicant-gait feed (it was misread as a "sequence number"; the type-2 record exists solely to ship it on change). Task #50's velocity-derived gait stays as the leg-channel driver meanwhile.
  • Types: 0 pose (Mover record + tail; orientation SAVED/RESTORED around the base call — it rides ONLY type 4), 1 damage zones (engine), 2 speedDemand, 3 leg-state+stability (write-through body dispatch both sides), 4 orientation+angular-velocity resync (euler-encoded, quat normalized), 5 knockdown (SetBodyAnimation 0x20 both sides), 6 death, 7 impact (fields only), 8 airborne/reverse selector. 5/6/7 share the 0x2c heat/throttle/fall-basis payload.
  • Senders wired byte-exact (the perf-loop + event map recovered from 27 binary call sites): gait transitions Force(8) (mech2 — already transcribed under the RequestActionFlags mislabel, masks matched the binary exactly), knockdown Force(1|0x20) (mech4), death Force(1|0x40) BEFORE SetMovementMode(9) (the disabled filter would mask it after), Reset Force(0x1f) + the binary zero-set (state 0, cycle/target speeds, poseSyncLatch), perf-loop deadbands: position → type 0, orientation deviation → type 4, speedDemand change → type 2, heat-level change → type 7 (snapshot @0x780). [T3 deadband thresholds — the model record's UpdatePositionDiffrence / UpdateTurnVelocityDiffrence / UpdateTurnDegreeDiffrence fields (+0x26..0x28) are the authentic constants, not yet streamed to the runtime members +0x768/+0x76c/+0x770.]
  • VERIFIED 2-node: types 2/3/4 flow while driving; kill → B emits ONE type-6 (simState=9 in the header) → A's replicant flips to MovementMode 9 and its own UpdateDeathState runs the wreck-sink loop on the OBSERVER's screen (smoke re-arm logged on A; NO double death transition, NO replicant VehicleDead) — the death SINK/burial gap (old item 3) is CLOSED. Respawn: Force(0x1f) burst snaps the replicant to the drop zone + un-wrecks it. Walking replicant un-regressed (run states 10→12, legCycle tracking, with type-3 records flowing). Solo clean.
  • Telemetry: [mrec-tx]/[mrec-rx] per non-pose record (BT_REPL_LOG). Note the type-1 rx line prints a bogus simState (the damage record's own layout at +0xC) — cosmetic.
  • New by-name members: poseSyncLatch@0x77c (dead-reckon re-base latch, consumed by FUN_004ab1c8), fallDirection@0x4a8 + fallScalar@0x4b4 [T4 names], heatLevelSnapshot@0x780. A TU-local stub type forced the BTMapperSpeedDemandRaw(void*) bridge (mech.cpp carries a local struct MechControlsMapper recon stub — typed cross-TU signatures mangle differently).

Subsystem update-record DIRECTION + the Torso pair (task #57, 2026-07-13) [T0/T1]

Engine semantics [T0 SIMULATE.cpp:270-297]: WriteUpdateRecord(record, model) PRODUCES the outgoing record from the object (master serialize; base sets recordLength=sizeof, stream advances by recordLength — variable-length records are the mechanism); ReadUpdateRecord(record) APPLIES the record into the object (replicant). The names read backwards — do not flip them. Base fns in every subsystem vtable: slot 6 = 41bd34 (Read/apply), slot 7 = 41c500 (Write/produce, 3 args).

  • Torso pair: slot 6 @004b6a78 = ReadUpdateRecord (apply: lastUpdateTime clock stamp + twistAtUpdate/twistVelocity/twistRate ← record +0x10/14/18). Slot 7 @004b6a1c = WriteUpdateRecordGhidra missed the function start; raw-disasm recovery: chains base, sets recordLength=0x1C, writes currentTwist/twistVelocity/twistRate → +0x10/14/18, snapshots twistAtUpdate = currentTwist.
  • The bug this fixed: the old recon had the APPLY body on the WRITE virtual (and no Write at all) — the master consumed its own uninitialized stream buffer (0xCDCDCDCD → twistAtUpdate = 4.316e8) and never serialized twist; the replicant MadCat pinned at 140° (the "torso ghost twist"). With the pair correct the replicant tracks targetTwist = twistAtUpdate + twistRate·elapsed cleanly (cur=0 when master still). [T2 live]
  • Residual (authentic): the initial full-state snapshot record does NOT carry the torso extras; the replicant's blind +0x10 read can pick up 0xCD fill ONCE at spawn. The binary does the same read; the twist clamp contains it (on the fixed-torso BLH it pins to ±0.01° — invisible). First real twist record overwrites it.

D1 RELAY MODE — internet play without rearchitecting (2026-07-18) [T2 live]

Opt-in relay/dispatcher topology so pods play over the internet behind NAT with no port forwarding, WHILE the authentic distributed-authority simulation is UNTOUCHED (each pod still masters its own mech; peers stay replicants). Env-gated: BT_RELAY=<host>:<consolePort> (host may be a DNS name; game port = +1; UDP = same number). Identity: BT_SELF=<exact [pilots] entry> claims a specific seat (NIC matching can't identify a pod across NAT); BT_SELF absent/auto ⇒ the RELAY assigns the seat (see RELAY-ASSIGNED SEATS below) — players never need to know a player number. BT_RELAY unset ⇒ every relay branch is dead ⇒ the classic mesh is byte-for-byte unchanged (mesh smoke-tested un-regressed each phase). Plan: ~/.claude/plans/partitioned-snuggling-piglet.md.

  • Topology: every pod makes ONE outbound TCP connection to the relay (an extension of tools/btconsole.py --relay) carrying both the console protocol (egg/launch — direction flipped, the pod dials OUT; the relay TERMINATES the legacy protocol) and all reliable game traffic, multiplexed by an 8-byte envelope {int32 route; uint32 length} (route ≥2 = unicast hostID, 1 = broadcast-except-sender, 2 HELLO/3 PEER_UP/4 PEER_DOWN). Plus one UDP socket for the unreliable channel.
  • The relay is DUMB: routes envelopes by hostID (validated against the egg [pilots] roster), holds no game state. PEER_UP/DOWN control frames on the client synthesize the SAME HostConnected/DisconnectedMessage the mesh accept path routes, so the "All connections completed!" gate + the whole app ladder run UNMODIFIED. Peers become VIRTUAL L4Hosts (INVALID_SOCKET, flipped online by PEER_UP).
  • O(N) upload fix: ExclusiveBroadcast builds ONCE and sends ONCE with the broadcast route — the relay fans out server-side (kills the mesh's N1× duplication at the seam where EntityBroadcastToReplicants already serialized once). Verified: relay tcp rx==tx, one copy per broadcast.
  • UDP channel = the 1995 NETNUB reliable/unreliable split RESTORED (not invented): Receiver::Message defaults messageFlags=ReliableFlag (RECEIVER.h:195); the ~60Hz update path sets flags=0 (ENTITY.cpp:590); Mode(UnreliableMode) at LoadingMission (APP.cpp:704, the 2007 port's Mode() was a no-op — now STORES the mode). The routing gate is (mode==UnreliableMode && !(flags & ReliableFlag)) — flag AND mode, so the map-stream creation messages (also flags=0 but flowing during the still-Reliable CreatingMission window) ride TCP for free. UDP envelope {route, fromHost, seq} + frame; relay learns each pod's endpoint per-datagram (NAT-rebind tolerant), forwards verbatim, TCP-wraps to a peer whose UDP endpoint is unknown (asymmetric-block fallback); per-sender seq gate drops stale/out-of-order (dead reckoning absorbs the gaps — that's what it's for). BT_RELAY_TCP_ONLY=1 = escape hatch (UDP-blocked networks degrade to the pure-TCP checkpoint automatically).
  • VERIFIED 2-node localhost relay: full ladder to RunningMission via PEER_UP; bidirectional replication (both mechs' MakeMessages cross the relay, paint ×2 each); during a drive the update stream flows on UDP (relay udp tx climbs 2→190+, TCP frozen at 53) and the peer's replicant tracks the master to sub-2u; 15% forced UDP drop (--udp-drop 15) stays coherent (replicant tracked to 0.1u); TCP-only fallback confirmed (udp-known drops the TCP-only pod); no crashes; mesh mode un-regressed. Recipe: relay python tools/btconsole.py --relay 1500 MP_RELAY.EGG --bind 127.0.0.1; pods BT_RELAY=127.0.0.1:1500 BT_SELF=10.99.0.N:1502 btl4 -egg MP_RELAY.EGG -net 1501 (egg [pilots] = arbitrary unique tags). Diag: relay [relay-stats] per-transport census; client BT_NET_TRACE.
  • LAN AUTO-DISCOVERY (2026-07-18) [T2]: BT_RELAY=auto — the pod broadcasts BTR1DISC on udp/15999 (broadcast + 127.0.0.1, 5×1s attempts); the relay answers BTR1HERE+; the replier's source IP + advertised port become the relay address. Zero-config for LAN players (the 90s cabinet model); broadcast never crosses routers so internet players keep the explicit host:port. Verified 2-node: both pods auto-discovered through a real interface and reached RunningMission. Relay side degrades gracefully if the discovery port is taken.
  • RELAY-ASSIGNED SEATS (2026-07-18) [T2]: no BT_SELF ⇒ the pod's NetworkManager ctor makes a short throwaway TCP dial to the relay GAME port and sends SEAT_REQUEST (route 6, empty); the relay reserves the lowest roster seat that is neither claimed (registered HELLO) nor reserved (60s window, so simultaneous joiners can't race onto one seat) and answers SEAT_ASSIGN (route 7, {int32 hostID} + NUL-terminated [pilots] tag) — the tag becomes relaySelf and everything downstream (egg self-match in StartConnecting, HELLO) proceeds exactly as if BT_SELF had been set. Roster full ⇒ SEAT_FULL (route 8) ⇒ clean Fail(). A real HELLO claiming the seat pops the reservation; a pod drop (PEER_DOWN) frees the seat for the next joiner. First-come-first-served — the arcade walk-up-to-a-pod model. Client: L4NetworkManager::RelayRequestSeat (L4NET.CPP); relay: _handle_game_frame SEAT_REQUEST branch (btconsole.py). Verified: 8/8 stub tests (distinct seats, FULL on exhaustion, claim clears reserve, duplicate-HELLO refused) + 2-node localhost e2e with NO BT_SELF on either pod → both seated, full ladder, launch, UDP flowing.
  • OPERATOR CONSOLE (2026-07-18): tools/btoperator.py (PySide6) — the lost 1995 operator station recreated: mission editor with ALL values validated live from BTL4.RES (tools/eggmodel.py: maps=type14∩26, colors/badges/patches from the type-25 vehicletable, vehicles from ModelLists), relay/mesh mode switch, live pod-status lights parsed from the console/relay stdout, one-click local instance launcher, and a single universal join.bat (+ join_lan.bat with BT_RELAY=auto) export — no BT_SELF inside, every player gets the SAME file, the relay seats them (players/). Wire logic stays in btconsole.py/eggmodel.py (GUI = UI + QProcess only). E2E-verified: app-driven relay session → 2 local pods → LAUNCH observed.
  • OPERATOR-SET CALLSIGNS (2026-07-18) [T2]: player callsigns are 1bpp BITMAPS in the egg, not text — [largebitmap]/[smallbitmap] list page names in bitmapindex order (1-based; Mission::LoadNameBitmaps MISSION.cpp:147); each [BitMap::Large::<X>] page = hex raster rows + x/y/width-in-words keys, large 128×32 (8 words), small 64×16 (4), MSB-first (0x8000 = leftmost pixel; parse GRAPH2D.cpp:340, bit order :558). Large renders on the score/ranking display (D3D textures, DPLRenderer::LoadNameBitmaps / SortAndReloadNameBitmaps L4VIDEO.cpp:9420/9512, mNameTextures[MAX_PLAYER_NAMES=12]); small = kill/damage-feed + radar/target labels (btplayer.cpp:1486, btl4rdr.cpp:1156). The textual name= pilot key is DEAD in the binary — the pilot-page parse (decomp part_014, BTL4MSSN) reads bitmapindex only [T1]. Historical bug found: every repo egg listed only ONE name bitmap (Aeolus) while pilot 2 pointed at index 2 → player 2+'s callsign never rendered (null-checked, silently blank). Now: the operator console has a Callsign roster column; on save eggmodel.make_callsigns rasterizes the text via Qt (auto-shrink to fit, NoAntialias, threshold to 1bpp) and EggDoc.set_callsigns writes the pages + lists + per-pilot name=/bitmapindex= (duplicate names get disambiguated page tags); get_callsign_rows decodes back (bit-exact round-trip in the self-test); validate() catches dangling bitmapindexes. MP_BHMC.EGG template fixed (authentic Aeolus raster preserved bit-exact, Boreas generated). Verified: eggmodel self-test 20/20; ASCII-art decode of app-generated VIPER/MONGOOSE rasters; live 2-node session on the generated egg.
  • STILL UNTESTED (config, not code): a real two-physical-machine / internet run (only the relay's console+game ports need to be reachable inbound — one forwarded port total; or a playit.gg-style tunnel in front of the relay, host-side only).

Remaining (P6 phase 4 / Phase 7)

Pod-LAN config: real-IP path VALIDATED (2026-07-15) — an egg with the machine's real LAN IP in [pilots] (not 127.0.0.1) connected + replicated + moved end-to-end (Connected to GameMachineHost at 10.0.0.46:1602, All connections completed!, peer telemetry flowing). So gethostbyname local-address matching (L4NET.CPP:2612, which also appends 127.0.0.1) + the WSAStringToAddressA IP:PORT parse (ResolveAddress) both work with real addresses — NOT localhost-locked. Recipe: egg [pilots] = each machine's LAN IP + its GAME port (= -net port + 1, e.g. 10.0.0.46:1502); each machine runs btl4 -egg lan.egg -net <consolePort>; console relay (btconsole.py lan.egg A_IP:1501 B_IP:1601) from any machine; open the TCP console+game ports in the Windows firewall. STILL UNTESTED: an ACTUAL two-physical-machine run (validated single-box via the real LAN IP), so firewall is the main first-attempt unknown. Affinity pinning is NOT needed across separate machines (the packet-jitter artifact is single-box only). Also: update-record velocity sourced from the body-channel projection (above). See open-questions. [T2 path / T3 two-box]

MP-front scout (task #45, 2026-07-09) — the P6 chain SURVIVES the combat/HUD rework [T2]

Re-ran the one-box smoke test on the current build (world-pick targeting, weapon groups, death transition, HUD all landed since P6): console egg → mesh → RunningMission on BOTH instances, 174+ ×144-byte update records EACH side (msgID=6), 2 mech trees per instance, 0 crashes over ~6 min. Gap map to a first playable LAN fight (in wiring order):

  1. Enemy selection — DONE (task #46). The world-pick used to test only gEnemyMech (the solo dummy, btplayer.cpp:791); now generalized to the live-mech registry candidate walk (BTGetTargetCandidates, all mechs != shooter) — see "Cross-pod combat step 1+2" above.
  2. Cross-pod damage — DONE (task #47). TakeDamage dispatched at the REPLICANT reroutes to the owning master (verified end-to-end: full cross-pod kill; the master takes real zone damage + runs its own death transition; task #42 placed the death dispatch victim-side — MP-correct by construction). See "Cross-pod combat step 1+2" above.
  3. Victim visuals on the shooter's screen — PARTIALLY resolved (user-observed 2026-07-09): the wreck SWAP does appear on the killer's screen, but the death SINK/burial does not (the collapse+ burial runs only in the owning pod's death transition). Fix path: transcribe the recovered Mech-level WriteUpdateRecord death/knockdown record types (5/6 — disasm preserved, task #51). 3b. Respawn cycle (task #52) — the AUTHENTIC cycle is now reconstructed [T1 @004c05c4 + engine PLAYER.cpp]: mech death transition (mech4.cpp) dispatches VehicleDead (ctor default deathCount = -1 = the death notification) to GetPlayerLink()BTPlayer:: VehicleDeadMessageHandler (@004c05c4 structure): MissionEnding(state 4) skips all; -1 → dedup on deathPending, ++deathCount, debit scenarioRole life, sever playerVehicle (the WRECK entity stays), re-post the same message at now + 5.0 s (5.0f @004c0830) stamped with deathCount; out of lives → the binary posts msgID 0x18 at +10 s (mission review; deferred). deathCount ≥ 0 / -2 with a vehicle in hand = acquired (clears deathPending); without one → engine base @0042db80 (PLAYER.cpp:214: closest non-"win" DropZone → AssignDropZone → 2s re-post probe) → DropZoneReply → CreatePlayerVehicle (a NEW mech; deathCount = 0) + viewpoint relink (Verify(viewpointEntity==NULL) is compiled out at DEBUG_LEVEL 0 — release overwrites, same as the 1995 build). @0049fe0c = roster-wide slot-11 DeathShutdown broadcast (the death transition already runs that pass). CORRECTIONS to the earlier scouting: BTL4.RES type 12 = InternalAudioStream (the engine's own resource-type table) — *int.scp are internal-AUDIO scripts, NOT per-mech intro scripts; and adrop (id 1994) is a type-14 MakeMessageStream = the map's four DropZone make records (0xE0 each: classID 3, origin, facing, name "two"/"three"/…), i.e. the respawn infrastructure. ⚠ CORRECTION (2026-07-09): the "blue whirlwind" IS in the game — it is the TRANSLOCATION SPHERE, and our port STUBS it. (My earlier "not a shipped asset" verdict was WRONG; it was wrong because I searched only RES resource NAMES + the keyword "whirlwind" — but the effect (a) loads tsphere.bgf by FILENAME via d3d_OBJECT::LoadObject, NOT through the RES table, and (b) is called "translocate," not "whirlwind.") The mechanism, fully recovered:
    • Asset: content/VIDEO/GEO/TSPHERE.BGF (2085-byte low-poly sphere; also in the pod install REL410/BT + RP + VWETEST/CYCLE). The exe string table has tsphere.bgf / tsphere.
    • Engine class: POVTranslocateRenderable (L4VIDRND.cpp:1749 / .h:638) — a VideoRenderable (Watcher) that loads tsphere.bgf and runs a state machine keyed on a StateIndicator "effect trigger" (the player's SimulationState dial) vs a control state: IdleState → (trigger hits control state) InitialCollapseState (sphere scales COLLAPSE_START_SCALE 100→1 over COLLAPSE_TIME 1.3s, then SetIsDead(true)) → WaitForReincarnateState (jitters via cos/sin TRANSLATE_LIMIT 2.0) → (trigger leaves control state = respawn) ExpandRevealState (scales 1→EXPAND_END_SCALE 150 over EXPAND_TIME 1.0s, SetIsDead(false)) → IdleState. Rotates throughout (ROTATE_RATE). I.e. the sphere COLLAPSES onto the mech at death and EXPANDS to reveal the reborn mech at respawn — exactly the "blue warp that comes to get you & respawns you."
    • Wiring (already present in our port!): btl4vid.cpp MakeEntityRenderables for a BTPlayerClassID REPLICANT creates new BTTranslocationRenderable(entity, Watcher, GetMainView(), sim_state=GetAttributePointer(SimulationState), drop_zone=GetAttributePointer("DropZoneLocation"), 1) (FUN_00458d2c, alloc 0x40, control state 1); our own POV instead gets BTPOVStartEndRenderable (the mission fade in/out). So peers watch each other's translocation sphere; your own death is a POV fade.
    • DONE + LOG-VERIFIED (2026-07-09, task #52): BTTranslocationRenderable was a no-op stub in btstubs.cpp; now reconstructed in btl4vid.cpp (state machine + d3d_OBJECT::LoadObject ("tsphere.bgf")), drawn direct from the render loop by BTDrawTranslocationSpheres (hooked in L4VIDEO.cpp beside BTDrawBeams, PASS_ALPHABLEND) — the same direct-draw accommodation the beams/ reticle use since our VideoComponent tree is partial. Trigger: the LOCAL player's own SimulationState dial, pulsed DropZoneAcquired(1)VehicleTranslocated(2) at respawn (btplayer.cpp create branch + a 1.4 s flip timer in PlayerSimulation), with the respawn origin written into the player's DropZoneLocation attribute (the sphere's draw position). Wiring (btl4vid.cpp MakeEntityRenderables, BTPlayerClassID) originally built the sphere only for REPLICANT players (third-party view) + a POV fade for self; extended to build it for the LOCAL player too, since peer player-attribute replication (SimulationState/DropZoneLocation) isn't wired (on a replicant both read 0xCD… uninitialised — confirmed via BT_TLOC_LOG). Verified 2-node: on B's respawn the sphere COLLAPSES (scale ~100→5) then EXPANDS (→150) at the valid drop-zone origin, deduped to ≤2 active, respawn cycle un-regressed, no render errors. Env gate BT_TLOC_LOG.
    • VISUAL DONE + LIVE-VERIFIED (task #52, follow-up): the on-screen look now matches the original cabinet photo (capture.png) — a smooth spinning lavender vortex with a bright core. The full geometry/material/render authority is now translocation-warp; headlines: tsphere is a 12-facet BICONE viewed ON-AXIS + spun in place (decomp FUN_00453dc4 Z-spin, which the WinTesla port had stubbed), coloured by the bintA cloud through a WIDE lavender ramp, drawn as SKY. The defect that hid it for the whole effort was NOT the warp code — it was a general texture-scroll precision-collapse bug in L4D3D::SetTextureScrolling (offset = scrollDelta × absolute_time → unbounded → UV precision loss → radial "spokes"), fixed with fmodf; that also cleans the scrolling beam grit. See reconstruction-gotchas §13. PEER WARP IS WIRED + VISIBLE (160b78e): an observer sees a peer's un-wreck warp via the world-anchored BTStartWarpEffect (mechdmg.cpp:1074, ReplicantInstance-gated; simulationState rides every record header). REMAINING [T3, non-gating]: the peer sphere is anchored to the peer's WORLD position, not the peer's authentic DropZoneLocation (that attribute isn't replicated) — a fidelity refinement. Also confirm the COLLAPSE (death-side) warp fires on every MP death path (force-damage test logged EXPAND reliably, COLLAPSE less so). NOTE the respawn-cycle SIM path is separate + done: DropZoneReply (FUN_004bffd0) → Mech::Reset (@0049fb74) + the level-2 "translocated" cockpit alarm; CreatePlayerVehicle (FUN_004bfcac, disassembled — make+link only, matches RP analog). The sphere is a pure RENDER-side watcher on the SimulationState dial, independent of that sim path. Mech::Reset (@0049fb74) does the full subsystem-reset sweep (mech4.cpp:1616 loops every subsystem → DeathReset(mode), restoring heat/power/ammo/charge) + heals all damage zones + ForceUpdate(0x1f). (Per-subsystem DeathReset bodies are authentically trivial/empty for some classes — the shipped build folds those overrides — so "fuller" reset behavior is per-subsystem, not a missing sweep.) Three respawn bugs found + fixed on the way (all [T2], 2-node force-damage verified): (i) death-latch un-latchIsMechDestroyed tested graphicAlarm>=9 alone; a later leg hit on the wreck rewrites the alarm to 4/3, un-latching → the death transition RE-RAN (double kill, double VehicleDead(-1)). Fixed to latch on movementMode 2||9 (see combat-damage.md "Death-latch correction"). (ii) score into severed vehicleBTPlayer::ScoreMessageHandler / ScoreInflictedMessageHandler deref playerVehicle unguarded; during the 5s dead window it is 0 → guard added (apply the award, skip the vehicle response). (iii) renderer LoadMission re-entry — the KILLER bug: MakeAndLinkViewpointEntity (APP.cpp:1265) calls videoRenderer->LoadMission on EVERY viewpoint-make, so the respawn re-runs DPLReadEnvironmentDPLReadINIPage; its light block Fails ("All lights must be defined on a single INI page") because sceneLightCount was never reset between reads (ctor-only init). Fixed in L4VIDEO.cpp DPLReadEnvironment: tear down the prior mission's sceneLight[] + sceneLightCount=0 before re-reading (the per-page invariant is intra-read only). This is a PORT-layer re-entrancy gap in the 2007 renderer, not BT logic. Caught via cdb bp ucrtbased!abort — a Fail/Check is an abort() MessageBox, not an AV, so sxe av misses it.
  4. Cross-pod beam visuals — DONE (task #51, 2026-07-09). The keepalive theory needed one correction: FUN_0041c350 = (a) queue the LOCAL deferred beam-effect callback (@0x4bac0c) on the app+0x34 manager (our per-weapon render walk plays that role) AND (b) set the subsystem DIRTY bit — which maps to the 2007 engine's updateModel/ForceUpdate(). The REPLICATION rides subsystem update records inside the mech's update message: the roster walk passes the entity's stream to every subsystem's PerformAndWatch; Emitter::WriteUpdateRecord (@004ba65c, corrected: record LENGTH not recordID; rec+0x30 = the TARGET's EntityID not a colour) serializes firingActive/beamEndpoint/beamFlag/targetID; the engine's Entity::UpdateMessageHandler routes records by subsystemID to Emitter::ReadUpdateRecord (@004ba568 — was MIStranscribed as TakeDamage; the whole weapon-family slot map was swapped: slot 6=Read, 7=Write, 9=TakeDamage — see mechweap.hpp). ServiceDischarge/ContinueDischarge call ForceUpdate() per keepalive tick + once at beam end. The render walk was extracted to Mech::DrawWeaponBeams (per-mech port cache) and runs for REPLICANTS. ⚠ two silent-failure traps fixed: the overrides originally used the class's own shadowing UpdateRecord typedef as the param type → never overrode the engine virtual (base ran, nothing serialized); and recordLength must be each level's sizeof or the stream framing misparses. VERIFIED 2-node: A fires 57 volleys → 225 records → B applies 225 → draws 414 replicant beams (PPC blue / laser red from A's replicant's gun ports). Solo un-regressed. The MECH-level records LANDED (task #1, 2026-07-11) — see the section below; this REMAINING note is closed.
  5. 2-window driving — RESOLVED (tasks #48/#51). Keyboard MP turning was restored at the root (the pilotArray[1] shrunk-span overrun fix, task #51); BT_KEY_NOFOCUS exists for automated harnesses. DEATHS scoring lights via the existing BTPostKillScore MP branch. Respawn is reconstructed (task #52 — see item 3b).

Key Relationships

Authentic coupled peer motion — DONE (task #50, 2026-07-14) [T1]

Decomp-verified (workflows w0odszxro/wh1h5gnmc, 3 make-or-break claims adversarially CONFIRMED; committed c52a1ad + default-promoted). The 1995 peer per-frame path FUN_004ab430 -> FUN_004ab1c8 (IntegrateMotion) drives motion SINGLE-SOURCE:

  • LINEAR position = GAIT. IntegrateMotion advances the BODY gait channel FUN_004a5678 (FUN_004a5028 is DEAD CODE, zero call sites), turns its cycleDistance into velocity {0,0,-cd/dt}, rotates by heading, integrates into projectedOrigin.linear@0x260 (part_012.c:14994/14997/15008); localOrigin.linear@0x100 is copied VERBATIM from it (FUN_00408440, part_012.c:15131 -- NOT a lerp). updateVelocity.linear is never used for position.
  • ANGULAR/heading = replicated-velocity slerp (the ONLY thing Mover::DeadReckon-style velocity extrapolation drives): projectedOrigin.angular@0x26c built from updateVelocity.angular@0x2d4 (FUN_004ab188), localOrigin.angular slerped toward it.
  • 0x260 aliasing: "motionDelta@0x260" and "projectedOrigin@0x260" are LITERALLY THE SAME field (base Mover::projectedOrigin, Origin 0x1c); "worldPose@0x26c" is its quaternion half. Our reconstruction's shadow names hid this and drove the false velocity-vs-gait "contradiction".
  • Re-anchor: motionEventVector@0x598 = updateOrigin - localOrigin captured on poseSyncLatch, DECAYED into localOrigin over ~_DAT_004ab9cc (~0.2s) -- no snap. Zeroed in idle states.
  • MASTER + PEER run the SAME predictor. The master's SEND-mirror (FUN_004a9b5c @0x4aab9c) advances projectedOrigin by IntegrateMotion(mj=0) fed the LAST-SENT bodyTargetSpeed, then deadbands |localOrigin-projectedOrigin|^2 vs UpdatePositionDiffrence@0x768. A gait-driven peer REQUIRES this gait mirror (a constant-velocity mirror under-sends -> tug-of-war, measured ratio 1.86).
  • 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): MEASURED SMALL / effectively resolved (2026-07-15). With the peer now on the BODY channel (96a896a, matching the master's body-channel mirror) + affinity pinning, an accel/decel soak (node B BT_AUTODRIVE+BT_DRIVE_SWEEP0 through STOP, node A BT_SNAPLOG) measures peer drift maxing at ~0.64u (median 0.54u, 18 events >0.5u over 48s), gently absorbed at k~0.24/frame -- SUB-UNIT, not the earlier ~2.9u (which predated the body-channel swap AND was measured without CPU-affinity, i.e. with packet-jitter-sparse records). At the noise floor; no fix warranted. The old "run the master mirror on a leg-channel prediction" plan is moot now the peer is on the body channel. Still open (cosmetic): 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).