Commit Graph
23 Commits
Author SHA1 Message Date
arcattackandClaude Fable 5 3c34ae6de6 KB: full staleness audit + sweep -- 35 verified corrections across 16 topics, 15 new glossary terms
Adversarially-verified audit (18 agents) against the task #46-#56 landings:
MP/current-state understatements, superseded punch readings, retired env
gates, dead paths, line-cite drift; env-gate hub table completed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:56:51 -05:00
arcattackandClaude Opus 4.8 7b0b4f64ab Diag: add BT_SHOT backbuffer dump + correct/park the -net render glitch (task #53)
Scouted the "-net node render glitch". Two prior framings were wrong and are
corrected in the KB: it is NOT device contention (retracted earlier), and it is
NOT a from-load glitch -- the trigger is a RESPAWN (user correction). It's a
post-respawn render-rebuild fault on the MP path.

Could not reproduce in 9 automated 2-node loads + a forced death/respawn (all
clean). Debug uninit is a deterministic 0xCDCDCDCD, so the intermittency implies
a race -- and the task #52 respawn fixes (LoadMission re-entry, viewpoint relink,
death-latch) plausibly already closed it. Parked as WATCH; re-open if it recurs.

Tooling (general, off by default): BT_SHOT=<path> dumps a per-instance backbuffer
PNG from the world-render path (L4VIDEO.cpp) every 90 frames -- lets us capture a
specific node's frame without foregrounding the window. Used with
scratchpad/hunt53.sh (N-run divergence hunter) to verify a fix if the glitch
returns.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:48:54 -05:00
arcattackandClaude Opus 4.8 0bfb3d4ab3 Warp: restore the translocation-vortex look + fix texture-scroll precision collapse (task #52)
The death/respawn "blue whirlwind" (tsphere) now matches the original cabinet
photo (capture.png): a smooth spinning lavender vortex with a bright core.

Root cause of the long-standing "radial spokes" artifact was NOT the warp code
but a general engine bug: L4D3D::SetTextureScrolling computed its texture-matrix
offset as scrollDelta * absolute_time, which grows unbounded and collapses UV
float precision -> a smooth scrolled cloud shatters into grainy radial steps.
Wrapped with fmodf(..., 1.0f) (identical under REPEAT tiling, full precision).
This also cleans the scrolling bexp beam grit and any other SCROLL material.

Visual reconstruction (verified against the real 45-vtx TSPHERE.BGF bicone,
offline-rasterized then ported):
  - view ON-AXIS (eye centred on the throat) + spin in place -> concentric rings
    (decomp FUN_00453dc4 does spin-about-local-Z + submit; the port had stubbed it)
  - bintA cloud through a WIDE lavender ramp at full contrast (drawn as SKY);
    no geometry "bands", no log-polar twist, no off-axis tornado (all discarded)
  - tessellate the 12-facet bicone smooth; isotropic + trilinear; ramp baked into
    the texture and drawn SELECTARG1(TEXTURE) to avoid double-tinting

Env knobs (BT_WARP_*) default to the verified values; BT_WARP_SELFTEST/SELFSHOT
are an off-by-default visual-verification harness (backbuffer frame dump).

Docs: new context/translocation-warp.md (geometry/material/visual/lifecycle/env);
reconstruction-gotchas.md gains the accumulated-time precision-collapse bug class;
rendering.md / multiplayer.md / decomp-reference.md / CLAUDE.md cross-linked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 14:53:50 -05:00
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