Files
BT411/game/reconstructed/matchlog.hpp
T
arcattackandClaude Opus 4.8 753540de96 MP match forensics: per-peer matchlog + relay auto-upload + matchcheck
For the 8-player playtest.  Damage authority is VICTIM-side, so no single
peer sees the whole match: every -net instance now writes a compact
machine-parseable matchlog_<date>_<time>_<pid>.txt (auto-armed by -net;
BT_MATCHLOG=1/0 overrides; solo silent), and at mission end a relay-mode
pod dials the relay game port (the seat-request throwaway-dial pattern,
envelope route -9) and streams the file back -- the relay saves every
peer's copy under matchlogs/ on the operator's machine, so testers send
nothing by hand.  tools/matchcheck.py <dir> reconciles all of them:
damage claimed (FIRE/PROJ/SPLASH/RAM, shooter-side) vs applied (DMG,
victim-side authoritative), kill/death replication across observers,
PLAYER_DEAD counts, scores, version skew, replicant-application and
unattributed-damage anomalies, mid-mission disconnects.

Hooks at the authoritative sites: Mech::TakeDamageMessageHandler (DMG,
post-base, zone + post-application damageLevel), MechWeapon::
SendDamageMessage (FIRE), projectile impact/splash/collision dispatch
(PROJ/SPLASH/RAM), wreck entry (DEATH -- a ONE-SHOT latch at
MovementMode 9, NOT the transition: a replicant arrives at 9 straight
from the type-6 record header and never runs the transition branch),
BTPlayer vehicle/death/score handlers (VEHICLE/PLAYER_DEAD/SCORE), zone
crit cascade (CRIT), APP.cpp RunningMission (MISSION), L4NET host
handlers (PEER_UP/PEER_DOWN).  Every line t=/w=/st=-stamped + fflushed.

Verified 2-node: mesh run pairs A's 22 FIRE 1:1 with B's 22 DMG (same
amounts, cylinder-resolved zones, ~200ms latency); force-damage kill run
logs 103 DMG + 3 DEATH inst=M + 3 PLAYER_DEAD across three respawn
cycles victim-side and the kill SCOREs shooter-side; relay-mode run
auto-uploaded BOTH peers' files at the mission-clock stop and matchcheck
produced the full report with exactly one (correct) anomaly -- the force
hook's claim-less damage, the unattributed-damage detector working.
Relay gained a route-specific 8MB cap for the upload frame (game frames
stay capped at 1600).

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

66 lines
3.3 KiB
C++

//===========================================================================//
// matchlog.hpp -- per-peer MP forensic combat log (PORT tooling, 2026-07-22)//
//---------------------------------------------------------------------------//
// One compact machine-parseable text file per game instance recording the
// combat-economy events this peer AUTHORITATIVELY owns. MP damage authority
// is VICTIM-side (A shoots B -> zone resolution + armor math run on B's
// machine), so no single peer sees the whole match: every player's file is
// collected after a session and tools/matchcheck.py reconciles them --
// damage dealt vs received per pair, kill/death symmetry across observers,
// out-of-economy amounts, replicant anomalies, disconnects.
//
// ARMING: BT_MATCHLOG=1 forces on, =0 forces off; unset -> auto-on when the
// process command line carries "-net" (any networked/console launch, which
// is every MP path -- join/join_lan/steam/menu). Solo stays silent.
// FILE: matchlog_YYYYMMDD_HHMMSS_<pid>.txt in the working directory (the
// install root the player bats run from) -- "send us the matchlog_* file".
//
// LINE FORMAT (every event):
// <TAG> t=<GetTickCount ms> w=<HH:MM:SS.mmm local> st=<appstate> <fields>
// fflushed per line: a crash mid-match keeps everything up to the crash.
//
// EVENT TAGS:
// HDR once at open: version/pid/machine/self/relay/cmdline
// MISSION the app enters RunningMission (engine APP.cpp)
// PEER_UP a game host came online (engine L4NET.CPP)
// PEER_DOWN a game host dropped (engine L4NET.CPP)
// VEHICLE a player got a mech (initial spawn + every respawn)
// FIRE shooter-side beam-weapon damage submission (per shot)
// PROJ shooter-side projectile/missile impact delivery
// SPLASH shooter-side splash (cluster) delivery to a bystander
// RAM shooter-side collision damage dispatch
// DMG VICTIM-side applied TakeDamage (zone resolved, post level)
// CRIT a critical subsystem destroyed by zone-damage cascade
// DEATH a mech's death transition ran (fires on every observer too;
// inst=M in the victim's own log is the authoritative one)
// PLAYER_DEAD the owning player's death notification (deaths counter)
// SCORE a score award folded into a player's currentScore
//===========================================================================//
#ifndef MATCHLOG_HPP
#define MATCHLOG_HPP
class EntityID;
// Armed? Lazy one-time init (opens the file + writes HDR on first yes).
int BTMatchLogActive();
// EntityID::GetHostID() is non-const while GetEntityID() returns a const
// value -- const-safe host/local extractors for the event lines.
int BTMatchHostOf(const EntityID &id);
int BTMatchLocalOf(const EntityID &id);
// The open log's file name ("" when not armed) -- consumed by the relay
// auto-upload (L4NET BTRelayUploadMatchLog) at mission end.
const char *BTMatchLogPath();
// Flush + close the file (upload wants a complete file on disk). Further
// BTMatchLog calls become no-ops.
void BTMatchLogClose();
// One event line; printf-style body appended after the standard prefix.
// Safe no-op when not armed, but call sites on per-shot/per-hit paths should
// still gate on BTMatchLogActive() to skip the varargs work.
void BTMatchLog(const char *tag, const char *format, ...);
#endif