Commit Graph
20 Commits
Author SHA1 Message Date
arcattackandClaude Opus 4.8 efa7aeb683 KB: retract the GPU-contention claim -- the -net node glitch is MP-path-specific (two solo instances both clean)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 22:21:35 -05:00
arcattackandClaude Opus 4.8 6ac960c883 KB: one-box 2-instance render glitch is a two-D3D9-apps-on-one-GPU artifact (solo is clean)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 22:14:40 -05:00
arcattackandClaude Opus 4.8 63c1c5a460 Implement the translocation sphere -- the "blue warp" respawn effect (task #52)
BTTranslocationRenderable was a no-op stub; now reconstructed over the engine's
POVTranslocateRenderable: loads tsphere.bgf and runs a collapse-on-arrival /
expand-on-reveal sphere (scale 100->1 over 1.3s, then 1->150 over 1.0s, rotating)
keyed on the player's SimulationState dial. Drawn direct from the render loop by
BTDrawTranslocationSpheres (beside BTDrawBeams, PASS_ALPHABLEND) -- the same
accommodation the beams/reticle use.

The asset loads by FILENAME (tsphere.bgf), not through the RES table -- which is
why every resource-name search missed the effect for three rounds.

Trigger wiring: btl4vid.cpp MakeEntityRenderables builds the sphere for the LOCAL
player (the authentic wiring builds it only for replicants + a POV fade for self,
but peer player-attribute replication isn't wired -- on a replicant SimulationState/
DropZoneLocation read uninitialised). btplayer.cpp pulses SimulationState
DropZoneAcquired->VehicleTranslocated at respawn (a 1.4s flip timer stands in for
the engine's +1s drop-zone re-post) and writes the respawn origin into the
DropZoneLocation attribute.

Verified 2-node (BT_TLOC_LOG): on respawn the sphere collapses (~100->5) then
expands (->150) at the valid drop-zone origin, deduped to <=2 active, respawn
cycle un-regressed, no render errors. Visual appearance (colour/size) still needs
a live look; the authentic see-others'-spheres path needs player replication.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 21:41:57 -05:00
arcattackandClaude Opus 4.8 6cd5f0b940 KB CORRECTION: the blue whirlwind IS in the game — the translocation sphere (tsphere.bgf)
My earlier 'not a shipped asset' verdict was wrong. The effect is POVTranslocateRenderable
(engine) / BTTranslocationRenderable (our port), which loads tsphere.bgf BY FILENAME (not via
the RES table, so every resource-name search missed it) and runs a collapse-on-death /
expand-on-respawn sphere keyed on the SimulationState dial. Wiring already exists in
btl4vid.cpp; the renderable is a no-op stub in btstubs.cpp. That's why it never shows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 21:15:48 -05:00
arcattackandClaude Opus 4.8 ef961a13c9 KB: deeper whirlwind sweep — string table, class registry, effect/camera systems all negative
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 20:43:42 -05:00
arcattackandClaude Opus 4.8 9abc839a43 KB: archive-check verdict — no whirlwind/warp recovery asset in REL410
Searched the full pod install (nick-games/tesla4pod_extracted REL410/BT;
BTL4.RES byte-identical to ours) and the whole nick-games archive: no
whirl/warp/tunnel/vortex asset by filename or RES resource name, no such
psfx in BTDPL.INI. The decomp drop path (DropZoneReply FUN_004bffd0) fires
no effect — only Mech::Reset + the level-2 "translocated" cockpit alarm.
BTPLAYER source is absent from all archives (binary-only). RP411 analog
uses InitialStasisFlag (sim gate, no visual). Verdict: the pod build's
respawn is mech-redrop + translocated alarm; the "blue whirlwind" is not
a shipped REL410 asset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 20:35:56 -05:00
arcattackandClaude Opus 4.8 83b3f31957 Whirlwind respawn: authentic death->drop-zone->recreate cycle (task #52)
Death transition (mech4) now dispatches VehicleDead(-1) to the owning
player; BTPlayer::VehicleDeadMessageHandler restructured to the authentic
@004c05c4 three-way branch: -1 = death bookkeeping + sever playerVehicle
(wreck stays) + 5s re-post; >=0 = engine drop-zone hunt -> DropZoneReply
-> CreatePlayerVehicle (new mech); -2 = the acquire probe. Guarded on a
DropZones group so a zone-less mission stays dead instead of aborting.

Three latent bugs the respawn path exposed, all fixed:
- IsMechDestroyed latched on 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/score, abort). Now latches on movementMode 2||9.
- Score handlers dereferenced the severed playerVehicle during the dead
  window; guarded.
- The console score flush routed a NetworkClient::Message through the
  player's Entity::Dispatch, which stamps entityID past the smaller struct
  -> /RTC1 stack overflow. Sent via application->SendMessage instead.
- Renderer LoadMission re-entry (per viewpoint-make) re-read the env INI;
  its light block Fail'd on stale sceneLightCount. Reset it in
  DPLReadEnvironment so the respawn's second read is clean.

Verified 2-node self-drive: B killed repeatedly by A respawns each time
(wreck stays), ticks + reloads + takes fresh damage on the new mech, no
abort/hang across multiple death->respawn cycles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 20:26:55 -05:00
arcattackandClaude Fable 5 e430e474cb Death gate: a destroyed mech takes no pilot input (ghost-drive fix)
User report from 2-window play: after dying, the pilot could still steer the
wreck around invisibly.  Zero throttle/turn/fire and the forced harnesses at
the gBTDrive source when IsMechDestroyed() -- deliberately INPUT-only, so the
gait/animation machinery keeps running and the death collapse plays out.

Also recorded in the KB: the killer's screen shows the wreck SWAP but not the
death SINK (needs the Mech-level death/knockdown update records, disasm
preserved from task #51), and the respawn-cycle scouting (type-12 intro
scripts are binary ref-records; adrop; DropZone + Mech::Reset machinery) for
the recovery-whirlwind reconstruction (task #52).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 19:18:11 -05:00
arcattackandClaude Fable 5 29035028fd Cross-pod beams: replicate emitter discharge via subsystem update records
User report: lasers only visible on the window firing them.  The peer's
replicant emitters never learned the master fired.

THE AUTHENTIC PIPE (decomp-verified):
- FUN_0041c350 (the "beam keepalive" ServiceDischarge/ContinueDischarge call)
  does TWO things: queue the LOCAL deferred beam-effect callback (@0x4bac0c)
  on the app+0x34 manager -- our per-weapon render walk already plays that
  role -- and set the subsystem DIRTY bit, which maps to the 2007 engine's
  updateModel / ForceUpdate().
- Replication rides SUBSYSTEM UPDATE RECORDS inside the mech's update message:
  the roster walk already hands the entity's stream to every subsystem's
  PerformAndWatch; Simulation::WriteSimulationUpdate serializes each requested
  updateModel bit; Entity::UpdateMessageHandler routes received records by
  subsystemID to the subsystem's ReadUpdateRecord.  All engine machinery --
  the missing pieces were the Emitter's serialize/apply pair + the triggers.

CORRECTIONS to the dormant task-33-era transcriptions (never exercised --
nothing ever set updateModel -- so the latent misreads never surfaced):
- The weapon-family VTABLE SLOT MAP was swapped: slot 6 = ReadUpdateRecord,
  7 = WriteUpdateRecord, 9 = TakeDamage (evidence: Mech hierarchy symmetry +
  body semantics; @004ba568 resolves an EntityID at rec+0x30 through the
  entity index -- record semantics, not Damage).  Renamed across MechWeapon /
  Emitter / ProjectileWeapon; the real Emitter::TakeDamage @004bafc8 is
  undecoded (inherits MechWeapon for now).
- Emitter/MechWeapon Write: `*record = 0x38/0x18` is the record LENGTH, not
  recordID; rec+0x30 is the TARGET's EntityID (GetEntityID()), not a colour --
  the old `CopyColor(targetEntity+0x184)` was also a databinding trap.
- OVERRIDE-SIGNATURE TRAP: the decls used each class's own shadowing
  UpdateRecord typedef as the param type, silently NOT overriding the engine
  virtual (the base ran instead; nothing would ever have serialized).
  Base-typed params (Simulation__UpdateRecord*), casts inside.
- Emitter::ReadUpdateRecord reconstructed (@004ba568): target EntityID resolve
  (drop unknown non-null targets), MechWeapon alarm apply chain, beam fields.
- ServiceDischarge/ContinueDischarge: ForceUpdate() per keepalive tick + one
  final record at beam end (turns the peer's beam off).
- Mech::DrawWeaponBeams extracted from the player-only drive block so the walk
  runs for REPLICANTS (+ per-mech gun-port cache -- the process-wide statics
  would have served the player's segment pointers as the replicant's muzzles).

VERIFIED 2-node: A fires 57 volleys -> 225 emitter records -> B applies all
225 -> B draws 414 beams (PPC blue / laser red, from A's replicant's own gun
ports).  Solo un-regressed (150 beams, kill chain, no crash).

Also preserved: the full Mech::WriteUpdateRecord @0x4a0c2c recovery
(reference/decomp/mech_writeupdate_004a0c2c.disasm.txt) with all 9 record
types decoded (pose/alarm/leg-state+heat with the body-channel write-through
re-sync, knockdown, death, impact, movementMode) -- transcription deferred;
it replicates remote knockdown/death/heat and was not needed for beams.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 19:04:34 -05:00
arcattackandClaude Fable 5 a110041a70 Fix "can't turn in MP": pilotArray[1] overrun stomped controlMode
User report from the first live 2-window session: throttle worked, A/D turn
did nothing.  [mppr] showed the input REACHING the mapper (stickX=-1) but
turnDemand=0 and controlMode = pointer garbage (73583232 / 217478632 --
different per instance), from frame 1, MP only (solo mode=0).

Hunted with a cdb hardware write-breakpoint (ba w4 armed at ctor time on
&controlMode, compiled offset +0x128 from the [mode-ctor] probe).  Caught the
writers red-handed: MechControlsMapper::BuildPilotArray / FillPilotArray.

ROOT CAUSE -- a shrunk-span array: the binary reserves a FIXED 10-slot pilot
block @0x15C..0x183 ((pilotArrayBuilt@0x184 - 0x15C)/4 = 10); the recon
declared `Pilot *pilotArray[1]` ("variable length"), so every write past
slot 0 stomps the members declared after it.  In MP, FillPilotArray wrote the
PEER's Player pointer into slot 1 = over controlMode -> the turn shaping
dispatched on pointer garbage -> turnDemand 0.  MASKED in solo: the zero-loop
overrun wrote 0 there, and 0 == BasicMode.  This was ALSO the real cause of
task #48's "turnDemand zeroes out in -net" observation (worked around then
with the direct goto-turn; attribution corrected in the KB).

Fix: pilotArray sized to the binary's own 10-slot span; ctor zeroes all
slots; BuildPilotArray clamps pilotCount to capacity (local + 9 remotes; an
8-pod game fits).  Diagnostics kept (BT_MODE_LOG ctor probe; [mppr] line now
prints mapper/&mode).

VERIFIED: MP session holds mode=0 throughout, turnDemand flows nonzero, the
mech turns (heading -1.33) and closes on the peer; solo kill chain intact.
KB: new "shrunk-span array" gotcha (reconstruction-gotchas 5) with the cdb
ba-w4 recipe; multiplayer.md task-#48 note corrected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 18:16:46 -05:00
arcattackandClaude Fable 5 d09cda6c36 MP: replicant gait animation -- peer mechs walk instead of sliding (task #50)
A peer's replicant mech slid around as a frozen statue: DeadReckon moved
localOrigin but nothing animated the legs.  Reconstruct the replicated-motion
display loop: after DeadReckon, derive 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 = forward dot against the -Z facing),
write it to controlsMapper->speedDemand (the LEG channel's live demand
source), and advance the LEG state machine for its JOINT writes only --
travel stays with DeadReckon, so position always follows the master and the
clip cadence matches the replicated speed.

Two replicant-only initialization holes found live (the master's equivalents
are primed per-frame in the PLAYER-only drive block):
- globalTimeScale/idleStrideScale read 0 -> clip advance time dt*0, the leg
  machine engaged (state 11) but the clip froze (legCycle stuck 0);
- reverseSpeedMax2 (the run-cycle rise-clamp @0x7a0; LoadLocomotionClips does
  NOT set it) read debug-heap garbage -> legCycleSpeed clamped to -4.3e8 on
  entering run state 13.  Primed both; legCycleSpeed sane-banded once.

VERIFIED 2-node (BT_GOTO driver + BT_REPL_LOG observer): the replicant runs
the full gait lifecycle -- stand -> walk(5,7) -> run(10,12), legCycle 7.5->34
tracking the master's speed, wind-down(8), Standing(0) when the master stops.
Solo un-regressed (goto->aim->kill chain intact, no crash).

projectedVelocity source-swap (the binary publishes the BODY channel's
smoothed projection instead of the actual travel velocity) evaluated and
DEFERRED: it risks a verified working replication feed for a smoothing
nuance; recorded in multiplayer.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 17:31:05 -05:00
arcattackandClaude Fable 5 9353d59eb9 Fix drive-to-range stall: BT_GOTO self-drives + steers correctly cross-net
The BT_GOTO beeline never actually closed on a target -- three separate faults:

1. It was gated behind `if(gBTDrive.forced)`, and `forced` was set ONLY by
   BT_AUTODRIVE -- a chicken-and-egg where the block that would drive was gated
   on the drive already being on.  So BT_GOTO alone just turned in place (the
   "stall").  Fix: BT_GOTO enables forced mode itself AND supplies its own
   forward throttle (a beeline must drive, not only steer), cutting the throttle
   inside a stop radius (weapon range for "enemy", so it holds + shoots).

2. Steering used the gDriveHeading SCALAR MIRROR (seeded to 0), but MP pilots
   spawn facing a non-zero heading -> it steered the WRONG way.  Fix: 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 zeroes out
   in -net mode (the mapper key-bridge only shapes the local viewpoint mech
   there) -- 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.

Verified 2-node one-box: A with BT_GOTO="enemy" ALONE (no autodrive) drives
dist 1673->231m, holds at weapon 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 still drives;
goto="enemy" resolves the enemy, mechPicks=59, no crash).  BT_GOTO_LOG traces
the beeline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 15:02:51 -05:00
arcattackandClaude Fable 5 0dfbb97dcc Fix torso-pitch aim ray: gyro-stabilized boresight from the mech torso
The aim/pick ray was the active RENDER EYE's view direction, which made a
mech unable to aim at a distant enemy:
  (a) it inherited the mech BODY pitch -- on a slope the body tilts ~8 deg to
      conform to terrain, so the boresight pointed that far DOWN and the pick
      ray hit the ground ~50-100m short of the target (groundPicks, never
      mechPicks); and
  (b) in CHASE view (the default) it was the elevated behind-the-shoulder
      camera, so the ray started over the mech's shoulder and flew high.

Fix (L4VIDRND DPLEyeRenderable::Execute, the BTSetAimCamera publish):
- LEVEL the boresight: drop the view-direction pitch and rebuild an upright
  basis (world +Y up) -- the guns fire along the mech's gyro-stabilized
  HORIZONTAL heading, not the terrain-pitched body.  Reticle X still carries
  the torso twist (BTTwistToReticleX); reticle Y any elevation.
- ANCHOR the ray origin at the mech's TORSO (myEntity->localToWorld
  translation + ~5) instead of the render eye, so it is correct in BOTH the
  chase (default) and cockpit views.

Verified end-to-end: solo, enemy 120m 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 757m, 2-node run).

mech4: BT_GOTO="enemy" test aid now chases the nearest peer of ANY instance
(solo dummy OR replicant), not just replicants.

Still open (locomotion, not aim/MP): BT_GOTO/BT_AUTODRIVE stall short of the
enemy, so an AUTOMATED 2-node kill via the real beam isn't demonstrated yet
(the FORCE_DMG hook still is).  A human can now aim correctly in the default
view.  Solo un-regressed; build clean; KB validator clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 14:11:54 -05:00
arcattackandClaude Fable 5 bac45851ad MP task #48 (partial): -net aim+drive already work; localize the real gap
Re-measured interactive -net combat on a 2-node run. The KB's "aim ray dead /
drive dead in -net" was STALE: A's aim ray is live (noRay=0, the active eye
publishes the aim camera) and A drives (BT_AUTODRIVE/BT_GOTO move it). The real
blocker to a human cross-pod kill via the beam path is NOT the netcode:

- Boresight inherits the mech BODY pitch. The aim ray = the active eye's view
  dir; on sloped terrain the body pitches to conform and the boresight points
  ~8 deg down ([EYE] up=(-.03,.99,-.14) == [pick] ldir.y=-0.145 every frame),
  so at range the ray hits terrain ~50-100m short of the enemy (mechPicks=0).
- Drive/goto stalls ~700m short before closing to pick/weapon range.
- PickRayHit vs a replicant is thus unconfirmed, but the replicant localToWorld
  IS correct (lstart magnitude == true A->B distance; the DeadReckon +
  localToWorld=localOrigin block already tracks the replicated position).

Change: add a gated BT_GOTO="enemy" test mode (beeline toward the nearest live
replicant) -- MP spawn coords vary per run so a fixed "x z" can't reliably face
the peer. KB (multiplayer.md) updated with the corrected findings + the real
remaining work (torso-pitch/level-boresight aim ray + drive-to-range).

Solo un-regressed; btl4.exe builds; KB validator clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 13:13:41 -05:00
arcattackandClaude Fable 5 54b6663b4d MP task #47 SOLVED: cross-pod damage + kill (validate the master mech)
Full cross-pod KILL now works: instance A (host 2) shoots B's mech (host 3)
dead over the network; B runs the death transition on its own screen. Verified
on a 2-node one-box run (BT_MP_FORCE_DMG on A): B's mech takes ~36 remote hits
of 12 and is DESTROYED, with wreck swap + explosion.

ROOT CAUSE (one bug): B's own MASTER mech was INVALID. Entity::Receive(Event*)
does `if(!IsValid()) event->Defer()` -- so a cross-pod-delivered TakeDamage that
arrived, resolved to B's real mech, and Posted, was DEFERRED forever; the handler
never ran. The reconstructed Mech::Make force-set ValidFlag only for replicants
(the P6 bring-up hack for the partial MakeReady handshake); a locally-created
master never got validated.

FIX (one line): Mech::Make now sets ValidFlag for the master too -- the
reconstructed ctor builds the mech synchronously, so it's safe. Nothing else
changed: targeting, the virtual victim->Dispatch, the replicant reroute, the
wire, receive, and id resolution were ALL already correct.

Two prior root causes were BOTH mis-diagnoses from the WRONG messageID:
Entity::TakeDamageMessageID is 18, not 21. Every earlier probe filtered
msgID==21, never matched a real TakeDamage, and mis-correlated the stray 21
hits' 3:19 / classID-48 against the real 3:22 hit -- inventing (a) a "cross-TU
Entity layout divergence" (disproven: offsetof identical in both TUs, sizeof
444, entityID@0x17C) and (b) a "localID corrupted on the wire". Both FALSE; the
id is correct end-to-end (3:22 = B's master).

- game/reconstructed/mech.cpp: validate master + replicant at Make.
- game/reconstructed/mech4.cpp: BT_MP_FORCE_DMG cleaned to a concise gated
  2-node cross-pod reproducer (real virtual Dispatch path).
- context/multiplayer.md, reconstruction-gotchas.md: corrected root cause +
  the validity-defers-cross-pod gotcha. Engine diagnostics reverted to clean.

Solo un-regressed (the solo enemy was already force-valid; the change only ADDS
ValidFlag). btl4.exe builds; full mission loop clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:52:26 -05:00
arcattackandClaude Fable 5 04a2049ce2 MP task #47: DISPROVE the Entity-layout root cause; correct the record
The prior commit (c78662a) filed cross-pod damage failure under a
"cross-TU Entity/EntityID layout divergence" (and the P5 base-region
audit). A compile-time offsetof probe injected into BOTH translation
units disproves that: game/reconstructed AND engine/MUNGA compute an
IDENTICAL Entity layout --

  sizeof(Entity)=444  offsetof(entityID)=380 (0x17C)
  offsetof(ownerID)=388  offsetof(simulationFlags)=32
  offsetof(Mech,entityID)=380  (Entity subobject at 0)

So Entity::Dispatch reads this->entityID at the SAME offset mech4's
GetEntityID() does -- there is no per-TU read difference, and the
logged 3:22 (mech4 candidate) vs 3:19 (engine Dispatch) cannot be one
object read two ways. Those lines were different objects/messages,
mis-correlated. The distinct, REAL P5 "base-region layout divergence"
(HARD_PROBLEMS.md) is about raw-offset stomps at this+0x2d4..0x2f0 --
far above entityID -- and is unrelated to #47.

Changes:
- Revert the committed BT_MP_NET engine diagnostics (ENTITY/EVENT/
  NTTMGR/RECEIVER/L4NET) to the clean a9c3e96 baseline -- those are
  the very instruments that produced the mis-correlated data.
- mech4 BT_MP_FORCE_DMG hook: dispatch via the real m->Dispatch(&td)
  path (no false id stamping); comment records the offsetof finding.
- context/multiplayer.md: layout-divergence RULED OUT; 3:22/3:19 marked
  unconfirmed; leading hypotheses reframed as H2 (wire host-relative
  (de)serialization) vs H3 (replicant id != master's registered key),
  to be distinguished by a 2-node run with per-message correlation.

Solo un-regressed (mech walks + targets, 0 faults). Cross-pod delivery
remains open, but the investigation is redirected off the wrong (large,
structural) layout-audit path onto the EntityID wire/id-assignment path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:09:50 -05:00
arcattackandClaude Fable 5 c78662a0f1 MP cross-pod damage ROOT-CAUSED: EntityID localID corrupted by hostID on the wire (task #47)
Traced the dispatched-message delivery end to end with BT_MP_NET /
BT_MP_FORCE_DMG. Everything works except one wire step:
- A's Entity::Dispatch reroutes the replicant's TakeDamage
  (application->SendMessage(ownerID=3, EntityManager, msg)); POST-Dispatch
  the message carries entityID=3:22 (host:local) -- the replicant's own
  ID, matching B's master (GetEntityID()==3:22). VERIFIED sent.
- On B the message ARRIVES, GetEntityPointer finds an entity, Posts it,
  the event drains (ProcessEventTask = ProcessOneEvent(0)), Event::Process
  runs, Receive finds+calls a handler. VERIFIED the full deliver chain.
- BUG: B receives entityID=3:19, NOT 3:22 -- the localID dropped by
  exactly the hostID (3) between A's send and B's receive. So
  GetEntityPointer(3:19) returns the WRONG entity (classID 48, not the
  mech 0xBB9), whose base handler ignores the unaimed hit -> 0 damage.
  Auto-replicated UPDATE records (msgID 18) arrive with the correct 3:22
  and find the mech, so the corruption is specific to the dispatched-
  message wire path/direction.

Next: the host-relative EntityID (de)serialization on the dispatched-msg
path (RoutePacket / packet EntityID encoding) -- diff vs the update path
which translates correctly. Diagnostics retained (all BT_MP_NET-gated,
off in solo -- verified: solo 22 hits, 0 probe noise).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:03:32 -05:00
arcattackandClaude Fable 5 a9c3e96f20 MP combat step 1 (target any peer) DONE; step 2 (cross-pod damage) localised (task #46)
STEP 1 -- target any peer mech: DONE + verified. A live-mech registry
(BTRegisterMech/BTDeregisterMech from the Mech ctor/dtor) collects
every mech -- player, dummy, and peer replicants. The boresight
world-pick walks BTGetTargetCandidates (all mechs != shooter, closest
ray hit) and the whole fire/damage block retargets from the solo
gEnemyMech to the picked hotTarget; missile/projectile validation
generalised via BTIsRegisteredMech. Verified one-box: instance A
enumerates B's mech as a live ReplicantInstance (20 zones, ownerID=3).
Solo un-regressed (pick->damage->kill clean, 28 hits + DESTROYED).

STEP 2 -- cross-pod damage: dispatch + engine reroute are CORRECT and
invoked with a valid owner, but the dispatched message doesn't reach
the master. Entity::Dispatch (ENTITY.cpp:244) reroutes a replicant's
msg via application->SendMessage(ownerID,...); BT_MP_FORCE_DMG proves A
dispatches at B's replicant with ownerID=3, yet B takes 0 STEP-6
damage. Corrects the KB's [T3] "Dispatch already reroutes": the reroute
FIRES; the break is downstream in the transport/delivery of a
dispatched entity-message (update records flow fine -- net-rx works).
Next MP task = that wire delivery. Diagnostics BT_MP_LOG /
BT_MP_FORCE_DMG retained.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 10:13:39 -05:00
arcattackandClaude Fable 5 48191d6fdf MP-front scout (task #45): P6 survives the combat rework; LAN-fight gap map
One-box smoke test re-run on the current build: console egg -> mesh ->
RunningMission both instances, 174+ x144-byte update records each side,
2 mech trees per instance, 0 crashes (~6 min). The movement-replication
milestone is intact after all the targeting/weapon/death changes.

Gap map to a first playable LAN fight recorded in
context/multiplayer.md (wiring order): (1) generalize the world-pick
from gEnemyMech to all peer mechs; (2) exercise the Dispatch reroute
for cross-pod TakeDamage; (3) victim state visuals on the shooter's
screen (zone replication or a death event); (4) cross-pod beam visuals
via the AUTHENTIC beam-keepalive messages (FUN_0041c350, templates
@0x511e6c/78 -- already stubbed in emitter.cpp); (5) 2-window driving +
DEATHS scoring; respawn deferred to the P5 teardown debt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 09:40:48 -05:00
arcattackandClaude Opus 4.8 f914fc040a context-system: complete migration -> CLAUDE.md is now a 160-line router (zero context lost)
Full migration of the 2236-line monolithic CLAUDE.md into the progressive-context
knowledge graph (per spark-lesson / expert-seed.md), so the deep RE knowledge loads
on-demand instead of every session.

ZERO CONTEXT LOST:
- docs/PROGRESS_LOG.md = the complete old CLAUDE.md, VERBATIM (byte-identical) -- the
  lossless safety net + the "full detail" quick-lookup fallback.
- 18 context/*.md topic files (1343 lines) digest every section (§1-3 -> project-overview,
  §4 -> content-archives, §5 -> asset-formats/bgf-format, §5a -> source-completeness,
  §5b/§8 -> wintesla-port, §7/§10 -> locomotion, §10a -> build-and-run, §10b ->
  reconstruction-method, §10c -> combat-damage + reconstruction-gotchas, §10d -> subsystems,
  render notes -> rendering, gauges -> gauges-hud, MP -> multiplayer, §9 -> open-questions).
- reference/glossary.yaml (53 terms). decomp-reference.md = the offsets/ClassIDs/addresses hub.

CLAUDE.md (160 lines) = router: identity, answer/reason protocols, quick-lookup table,
evidence tiers (T0 engine-truth / T1 decompiled+verified / T2 reconstructed+runtime /
T3 guarded / T4 hypothesis), conventions + DO-NOT (the systemic bug classes), structure.
Retains the load-bearing work directives (build recipe pointer, "keep current" mandate).

Knowledge graph validates CLEAN (scratchpad/checkctx.py -- all [[links]] + quick-lookup +
docs refs resolve; [[name]] -> topic file or glossary term). docs/*.md ledgers stay as the
detailed logs; context/*.md are the curated digests that route into them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 22:19:50 -05:00