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>