完善客户端功能并同步差距文档
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
// CLIENT-GAP §3.2 — EntityStore TCP state queue: server-frame release gate,
|
||||
// __CanProcessNetworkStatePacket gate, and the FUNC_* switch with its 1.0 / 50.0
|
||||
// hard thresholds (no lerp, no jitter buffer). Drives the protocol-neutral
|
||||
// mutation API directly (same path the classic backend uses).
|
||||
#include "../src/net/entity_store.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
using namespace mtnet;
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(c, msg) \
|
||||
do { \
|
||||
if (!(c)) { \
|
||||
std::fprintf(stderr, "FAIL: %s\n", msg); \
|
||||
++g_fail; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void spawn(EntityStore &es, uint32_t vid, float x, float y) {
|
||||
es.mut_spawn(vid, 101, 0, "Mob", x, y, 0.0f, 0.0f, 200 /*moving_speed*/, 0);
|
||||
}
|
||||
|
||||
int main() {
|
||||
// --- 1) server-frame release gate ---------------------------------------
|
||||
{
|
||||
EntityStore es;
|
||||
es.set_now(0);
|
||||
spawn(es, 10, 0, 0);
|
||||
es.set_server_frame_ms(1000);
|
||||
// command timestamped in the future: stays queued, entity does not move.
|
||||
es.mut_move(10, 0.0f, FUNC_MOVE, 500.0f, 0.0f, 0, 0, /*chk_time*/ 2000);
|
||||
CHECK(es.get(10)->state_queue.size() == 1, "future cmd stays queued");
|
||||
CHECK(!es.get(10)->moving, "future cmd: not moving yet");
|
||||
es.process_states();
|
||||
CHECK(es.get(10)->state_queue.size() == 1, "still queued below chk_time");
|
||||
|
||||
es.set_server_frame_ms(2000); // clock reaches the command time
|
||||
es.process_states();
|
||||
CHECK(es.get(10)->state_queue.empty(), "cmd released once frame clock catches up");
|
||||
CHECK(es.get(10)->moving && es.get(10)->func == FUNC_MOVE, "released FUNC_MOVE walks");
|
||||
|
||||
// frame clock 0 == "not aligned yet" -> release immediately.
|
||||
EntityStore es0;
|
||||
es0.set_now(0);
|
||||
spawn(es0, 11, 0, 0);
|
||||
es0.mut_move(11, 0.0f, FUNC_MOVE, 500.0f, 0.0f, 0, 0, /*chk_time*/ 9999);
|
||||
CHECK(es0.get(11)->state_queue.empty() && es0.get(11)->moving,
|
||||
"frame clock 0 releases regardless of chk_time");
|
||||
}
|
||||
|
||||
// --- 2) FUNC_WAIT: 1.0 threshold (snap-align vs. walk) ------------------
|
||||
{
|
||||
EntityStore es;
|
||||
es.set_now(0);
|
||||
spawn(es, 20, 0, 0);
|
||||
// within 1.0 -> snap align, no walk.
|
||||
es.mut_move(20, 90.0f, FUNC_WAIT, 0.5f, 0.0f, 0, 0, 0);
|
||||
CHECK(!es.get(20)->moving, "FUNC_WAIT dir_len<=1.0: no walk");
|
||||
CHECK(std::abs(es.get(20)->x - 0.5f) < 1e-3f, "FUNC_WAIT dir_len<=1.0: snapped to Dst");
|
||||
CHECK(es.get(20)->func == FUNC_WAIT && std::abs(es.get(20)->angle - 90.0f) < 1e-3f,
|
||||
"FUNC_WAIT snap: func + heading applied");
|
||||
// beyond 1.0 -> walk there, m_kMovAfterFunc == WAIT.
|
||||
es.mut_move(20, 0.0f, FUNC_WAIT, 100.0f, 0.0f, 0, 0, 0);
|
||||
CHECK(es.get(20)->moving && es.get(20)->func == FUNC_MOVE, "FUNC_WAIT dir_len>1.0: walks");
|
||||
CHECK(es.get(20)->mov_after_func == FUNC_WAIT, "FUNC_WAIT walk: after-func WAIT");
|
||||
CHECK(es.get(20)->skip_collision, "network-driven walk enables skip-collision");
|
||||
es.set_now(100000);
|
||||
es.tick();
|
||||
CHECK(!es.get(20)->moving && es.get(20)->func == FUNC_WAIT, "FUNC_WAIT walk: settles to WAIT");
|
||||
CHECK(!es.get(20)->skip_collision, "skip-collision cleared on arrival");
|
||||
}
|
||||
|
||||
// --- 3) FUNC_COMBO: 50.0 threshold + after-arrival action --------------
|
||||
{
|
||||
EntityStore es;
|
||||
es.set_now(0);
|
||||
spawn(es, 30, 0, 0);
|
||||
// near (dir_len 20 < 50) -> snap + act now, arg is on the row's func.
|
||||
es.mut_move(30, 45.0f, FUNC_COMBO, 20.0f, 0.0f, 0, /*arg*/ 7, 0);
|
||||
CHECK(!es.get(30)->moving && es.get(30)->func == FUNC_COMBO,
|
||||
"FUNC_COMBO dir_len<50: snap + act");
|
||||
CHECK(std::abs(es.get(30)->x - 20.0f) < 1e-3f, "FUNC_COMBO near: snapped to Dst");
|
||||
|
||||
// far (dir_len 200 >= 50) -> walk first, combat + arg deferred to arrival.
|
||||
es.mut_move(30, 0.0f, FUNC_COMBO, 220.0f, 0.0f, 0, /*arg*/ 9, 0);
|
||||
CHECK(es.get(30)->moving && es.get(30)->func == FUNC_MOVE, "FUNC_COMBO dir_len>=50: walks");
|
||||
CHECK(es.get(30)->mov_after_func == FUNC_COMBO && es.get(30)->mov_after_arg == 9,
|
||||
"FUNC_COMBO far: combo + motion index latched for arrival");
|
||||
es.set_now(100000);
|
||||
es.tick();
|
||||
CHECK(!es.get(30)->moving && es.get(30)->func == FUNC_COMBO,
|
||||
"FUNC_COMBO far: combo runs on arrival");
|
||||
CHECK(es.get(30)->mov_after_func == FUNC_WAIT, "after-func latch cleared post-arrival");
|
||||
}
|
||||
|
||||
// --- 4) __CanProcessNetworkStatePacket gate (dead / knocked-down) ------
|
||||
{
|
||||
EntityStore es;
|
||||
es.set_now(0);
|
||||
spawn(es, 40, 0, 0);
|
||||
es.mut_knockdown(40, true);
|
||||
es.mut_move(40, 0.0f, FUNC_MOVE, 300.0f, 0.0f, 0, 0, 0);
|
||||
CHECK(es.get(40)->state_queue.size() == 1 && !es.get(40)->moving,
|
||||
"knocked-down: state cmd held, not applied");
|
||||
es.mut_knockdown(40, false);
|
||||
es.process_states();
|
||||
CHECK(es.get(40)->state_queue.empty() && es.get(40)->moving,
|
||||
"recovered: held state cmd releases");
|
||||
|
||||
es.mut_dead(41); // unknown vid: no-op, must not crash
|
||||
spawn(es, 42, 0, 0);
|
||||
es.mut_dead(42);
|
||||
es.mut_move(42, 0.0f, FUNC_MOVE, 300.0f, 0.0f, 0, 0, 0);
|
||||
CHECK(!es.get(42)->moving && es.get(42)->state_queue.size() == 1,
|
||||
"dead: state cmd held");
|
||||
}
|
||||
|
||||
// --- 5) queue preserves order and drains multiple due commands --------
|
||||
{
|
||||
EntityStore es;
|
||||
es.set_now(0);
|
||||
es.set_server_frame_ms(0);
|
||||
spawn(es, 50, 0, 0);
|
||||
es.mut_move(50, 0.0f, FUNC_WAIT, 0.2f, 0.0f, 0, 0, 0); // snap (near)
|
||||
es.mut_move(50, 0.0f, FUNC_ATTACK, 10.0f, 0.0f, 0, 3, 0); // near attack -> snap + act
|
||||
CHECK(es.get(50)->state_queue.empty(), "both due cmds drained in one pass");
|
||||
CHECK(es.get(50)->func == FUNC_ATTACK && std::abs(es.get(50)->x - 10.0f) < 1e-3f,
|
||||
"last cmd wins: ATTACK at (10,0)");
|
||||
}
|
||||
|
||||
// --- 6) staleness valve: chk_time far ahead of the frame clock -----------
|
||||
// Reference StateProcess has no cap; ours releases a command whose chk_time
|
||||
// is > STATE_QUEUE_MAX_WAIT_MS ahead of the (unvalidated) server-frame clock
|
||||
// so a wrong-timebase dwTime can never freeze the actor forever.
|
||||
{
|
||||
EntityStore es;
|
||||
es.set_now(0);
|
||||
es.set_server_frame_ms(1000);
|
||||
// exactly at the cap -> still gated.
|
||||
spawn(es, 60, 0, 0);
|
||||
es.mut_move(60, 0.0f, FUNC_MOVE, 500.0f, 0.0f, 0, 0,
|
||||
/*chk_time*/ 1000 + STATE_QUEUE_MAX_WAIT_MS);
|
||||
CHECK(es.get(60)->state_queue.size() == 1 && !es.get(60)->moving,
|
||||
"chk_time == frame+cap: still gated");
|
||||
// one ms past the cap -> treated as out-of-band, released now.
|
||||
spawn(es, 61, 0, 0);
|
||||
es.mut_move(61, 0.0f, FUNC_MOVE, 500.0f, 0.0f, 0, 0,
|
||||
/*chk_time*/ 1000 + STATE_QUEUE_MAX_WAIT_MS + 1);
|
||||
CHECK(es.get(61)->state_queue.empty() && es.get(61)->moving && es.get(61)->func == FUNC_MOVE,
|
||||
"chk_time past cap: released immediately (staleness valve)");
|
||||
}
|
||||
|
||||
if (g_fail == 0) {
|
||||
std::printf("PASS: net_state_queue_test (§3.2 TCP state queue / thresholds / gates)\n");
|
||||
}
|
||||
return g_fail == 0 ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user