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>
66 lines
3.3 KiB
C++
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
|