group2 0.1.0
CSE 125 Group 2
Loading...
Searching...
No Matches
Bot.hpp
Go to the documentation of this file.
1
14
15#pragma once
16
19#include "network/Client.hpp"
20// PR-23: prediction parity with the real client. Header-only, so just
21// pulling in InputRingBuffer is enough at the .hpp level.
23
24#include <SDL3/SDL_stdinc.h>
25
26#include <atomic>
27#include <cstdint>
28#include <glm/vec3.hpp>
29#include <optional>
30#include <string>
31#include <thread>
32
33// (Bot members keep the trailing-underscore convention used elsewhere
34// in this file; the .clang-tidy member rule trips on it but the existing
35// code base treats trailing-underscore as the established style.)
36
37class Bot
38{
39public:
41 static constexpr int k_tickHz = 128;
42
43 Bot() = default;
44 Bot(const Bot&) = delete;
45 Bot& operator=(const Bot&) = delete;
46 Bot(Bot&&) = delete;
47 Bot& operator=(Bot&&) = delete;
48 ~Bot();
49
55 bool init(const std::string& host, Uint16 port, int botId);
56
63 void setSimulatedLatencyMs(int totalMs);
64
68 void setSimulatedLossPercent(int percent);
69
72 void start(const std::atomic<bool>& stopFlag);
73
76 void join();
77
85 [[nodiscard]] float getCurrentRttMs() const;
86
90 [[nodiscard]] bool isReady() const { return ready_.load(std::memory_order_relaxed); }
91
92private:
94 void runLoop(const std::atomic<bool>& stopFlag);
95
98 std::thread thread_;
100 uint32_t predictTick_ = 0;
101 std::optional<registry_serialization::Loader>
103 std::optional<entt::entity> mappedLocalPlayerEntity_;
104 int botId_ = 0;
105 bool initialized_ = false;
106
110 std::atomic<bool> ready_{false};
111
112 // ── PR-23: prediction + reconciliation (parity with real client) ────
113 //
114 // Client-side prediction lets the bot simulate its OWN movement
115 // locally each physics tick. Pre-PR-23 the bot's `Position` only
116 // moved when a server snapshot applied, which meant the bot's view
117 // of its own position lagged the server's by `RTT/2 + interpDelay`.
118 // For lag-comp / hit-reg testing under simulated RTT that drift was
119 // a measurement artifact (not a real-client behaviour), polluting
120 // the PR-22 ray-origin-desync metric with self-position lag the
121 // real client doesn't have.
122 //
123 // The bot now mirrors `client/game/Game.cpp::iterate`:
124 // * each physics tick, push (predictTick_, input_) into ring,
125 // run `systems::runPrediction` on the bot's local entity
126 // * after `client_.poll`, if a snapshot just applied, call
127 // `systems::runReconciliation` from `serverAckedClientTick`
128 // forward to `predictTick_`.
129 // Same code paths the real client runs — divergence between bot
130 // and real client is now data-only (different inputs), not
131 // architectural.
133
138 bool localPlayerReady_ = false;
139
147 std::uint32_t snapshotTick, const std::uint8_t* bytes, Uint32 size, Uint64 captureNs, std::uint32_t& ackedTick);
149 [[nodiscard]] std::optional<entt::entity> getLocalPlayerEntity() const;
150
151 // ── PR-18: per-bot snapshot observation log ──────────────────────────
152 //
153 // Opened from `GROUP2_BOT_OBS_CSV_PREFIX` at init() if set; one CSV
154 // file per bot, named `<prefix><botId>.csv`. Each row records one
155 // remote-entity sighting from the bot's perspective:
156 //
157 // wallTimeNs,observerBotId,observedClientId,posX,posY,posZ
158 //
159 // Written ONCE per snapshot apply (not once per tick) — gated by
160 // `Client::consumeSnapshotApplied()` so the row count matches the
161 // snapshot stream, not the tick stream.
162 //
163 // The companion server-side `GROUP2_SERVER_TRUTH_CSV` log records the
164 // SAME columns from the server's perspective (with `serverTick` for
165 // alignment). An offline Python tool (`scripts/netsync-analyze.py`)
166 // joins the two by wall-clock + clientId, interpolates between
167 // adjacent server samples, and reports euclidean desync per bot per
168 // entity per (sim RTT, sim loss) bucket.
169 //
170 // This framework would have caught the FragmentReassembler stuck-
171 // state bug (PR-17) instantly: bot's observation log freezes while
172 // server truth keeps moving → enormous desync values. Going forward
173 // it lets us A/B-test future netcode changes (quantization, AoI
174 // culling, snapshot-rate changes) with hard numbers, not feel.
175 std::FILE* obsCsv_ = nullptr;
176
177 // ── PR-21: bot-side shot-intent log ──────────────────────────────────
178 //
179 // Opened from `GROUP2_BOT_SHOTS_CSV_PREFIX` at init() if set. One
180 // CSV per bot, named `<prefix><botId>.csv`. Each row records ONE
181 // intentional fire (rising edge of `input.shooting`) with the
182 // information needed to join against the server-side
183 // `server_shots.csv` produced by PR-18b's `perf::shotlog`:
184 //
185 // wallTimeNs,shooterClientId,shotInputTick,
186 // originX,originY,originZ,
187 // dirX,dirY,dirZ,
188 // intendedTargetClientId,
189 // intendedTargetX,intendedTargetY,intendedTargetZ,
190 // intendedTargetDist
191 //
192 // The matching key is `(shooterClientId, shotInputTick)` — the
193 // server stamps the same pair on every shot in `server_shots.csv`.
194 // The Python analyzer joins them and reports per-RTT hit rate,
195 // intended-vs-actual target distribution, region match, etc.
196 std::FILE* shotsCsv_ = nullptr;
197 bool prevShootingForLog_ = false;
198
201 void openShotsLog();
202
215 void writeShotIntent(std::uint16_t shooterClientId,
216 std::uint32_t shotInputTick,
217 const glm::vec3& origin,
218 const glm::vec3& direction,
219 std::uint16_t intendedTargetClientId,
220 const glm::vec3& intendedTargetPos,
221 float intendedTargetDist,
222 bool botRayHit,
223 std::uint16_t botHitClientId,
224 const glm::vec3& botHitPos,
225 float botHitDist);
226
228 void closeShotsLog() noexcept;
229
232 void openObservationLog();
233
238 void writeObservationLog();
239
241 void closeObservationLog() noexcept;
242};
TCP client for connecting to the game server.
Per-tick history of stamped input snapshots, keyed by clientPredictTick.
Per-tick player input snapshot for networking and prediction.
Shared ECS registry type alias for the game engine.
entt::registry Registry
Shared ECS registry type alias.
Definition Registry.hpp:11
std::optional< registry_serialization::Loader > snapshotLoader_
Incremental snapshot loader; created on first snapshot apply.
Definition Bot.hpp:102
void setSimulatedLossPercent(int percent)
Apply a simulated UDP packet-loss percentage to the bot's UDP path.
Definition Bot.cpp:293
void join()
Block until the worker thread exits.
Definition Bot.cpp:359
std::FILE * obsCsv_
Definition Bot.hpp:175
void writeShotIntent(std::uint16_t shooterClientId, std::uint32_t shotInputTick, const glm::vec3 &origin, const glm::vec3 &direction, std::uint16_t intendedTargetClientId, const glm::vec3 &intendedTargetPos, float intendedTargetDist, bool botRayHit, std::uint16_t botHitClientId, const glm::vec3 &botHitPos, float botHitDist)
Append a shot-intent row.
Definition Bot.cpp:139
bool initialized_
True once init() succeeded; gates run().
Definition Bot.hpp:105
Registry registry_
Snapshot apply target — never read.
Definition Bot.hpp:97
void writeObservationLog()
Walk the registry for view<Position, ClientId>, write one row per replicated player entity to the bot...
Definition Bot.cpp:72
~Bot()
Definition Bot.cpp:41
int botId_
Log prefix.
Definition Bot.hpp:104
std::FILE * shotsCsv_
Definition Bot.hpp:196
std::thread thread_
Worker thread; joined in dtor or join().
Definition Bot.hpp:98
Bot(const Bot &)=delete
static constexpr int k_tickHz
Default tick rate; matches the server's physics tick rate.
Definition Bot.hpp:41
void setSimulatedLatencyMs(int totalMs)
Apply a simulated round-trip latency on the bot's UDP path.
Definition Bot.cpp:288
bool localPlayerReady_
Set true once the bot maps the server-assigned local player.
Definition Bot.hpp:138
bool prevShootingForLog_
Rising-edge detector for fire log.
Definition Bot.hpp:197
bool isReady() const
PR-1: true once the bot's worker has logged at least one finished tick.
Definition Bot.hpp:90
uint32_t predictTick_
Monotonic tick counter, stamped onto each input.
Definition Bot.hpp:100
void runLoop(const std::atomic< bool > &stopFlag)
Worker-thread main loop: send input + poll, sleep to next tick.
Definition Bot.cpp:375
std::optional< entt::entity > getLocalPlayerEntity() const
Return the bot's local-registry player entity, or nullopt before the first snapshot maps it.
Definition Bot.cpp:253
void closeObservationLog() noexcept
Flush + close the CSV if open. Safe to call from dtor.
Definition Bot.cpp:105
void openObservationLog()
Open the CSV if GROUP2_BOT_OBS_CSV_PREFIX is set.
Definition Bot.cpp:53
bool applyIncomingSnapshot(std::uint32_t snapshotTick, const std::uint8_t *bytes, Uint32 size, Uint64 captureNs, std::uint32_t &ackedTick)
Deserialize a snapshot and update the bot's registry; maps the local entity on first apply.
Definition Bot.cpp:217
Bot & operator=(const Bot &)=delete
bool init(const std::string &host, Uint16 port, int botId)
Connect to the server.
Definition Bot.cpp:260
Bot()=default
std::atomic< bool > ready_
PR-1 (server-perf): set true after the first successful poll inside runLoop.
Definition Bot.hpp:110
Bot(Bot &&)=delete
InputRingBuffer inputRing_
Definition Bot.hpp:132
void openShotsLog()
Open the shot-intent log if GROUP2_BOT_SHOTS_CSV_PREFIX is set.
Definition Bot.cpp:115
Bot & operator=(Bot &&)=delete
std::optional< entt::entity > mappedLocalPlayerEntity_
Local-registry entity for this bot's player, once mapped.
Definition Bot.hpp:103
float getCurrentRttMs() const
PR-1 (server-perf): current smoothed RTT in ms.
Definition Bot.cpp:365
void start(const std::atomic< bool > &stopFlag)
Spawn the worker thread.
Definition Bot.cpp:298
Client client_
Underlying TCP client (TCP today; UDP after Phase 3).
Definition Bot.hpp:96
InputSnapshot input_
Reused per-tick input scratch.
Definition Bot.hpp:99
void closeShotsLog() noexcept
Flush + close the shot log if open. Safe from dtor.
Definition Bot.cpp:180
void setupLocalPlayerCallback()
PR-23: set up the snapshot callback that emplaces LocalPlayer + InputSnapshot + PreviousPosition + Pl...
Definition Bot.cpp:206
TCP stream client — sends input to the server and receives state updates.
Definition Client.hpp:69
Definition InputRingBuffer.hpp:27
One tick of player input, stamped with the tick it was sampled on.
Definition InputSnapshot.hpp:17