fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
This commit is contained in:
@@ -77,6 +77,16 @@ int main() {
|
||||
CHECK(sync_echo.time == 2000u + 2u * 10u && sync_echo.delta == 0,
|
||||
"later handshake time fields are converged");
|
||||
CHECK(out[sizeof(Handshake)] == SEQUENCE_TABLE[0], "time-sync uses next sequence byte");
|
||||
|
||||
// The reference keeps the old active clock until the blank
|
||||
// HANDSHAKE_OK arrives; the later handshake is only a pending proposal.
|
||||
const uint32_t before_ok = s.server_frame_ms();
|
||||
CHECK(before_ok < 100000u, "resync does not activate the new server clock early");
|
||||
uint8_t ok = HDR_GC_TIME_SYNC;
|
||||
s.feed(&ok, 1);
|
||||
const uint32_t after_ok = s.server_frame_ms();
|
||||
CHECK(after_ok >= 2000u && after_ok < 10000u,
|
||||
"HANDSHAKE_OK applies the staged server clock correction");
|
||||
}
|
||||
|
||||
// -------------------------------------------- server frame clock (§3.2)
|
||||
@@ -191,6 +201,101 @@ int main() {
|
||||
"dynamic: text tail present");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------- static whisper head + text tail
|
||||
{
|
||||
ClassicStream s;
|
||||
std::vector<uint8_t> headers;
|
||||
s.on_packet = [&](uint8_t h, const uint8_t *, uint32_t) {
|
||||
headers.push_back(h);
|
||||
return true;
|
||||
};
|
||||
GCWhisperHead whisper{};
|
||||
whisper.header = HDR_GC_WHISPER;
|
||||
whisper.type = 1;
|
||||
std::strcpy(whisper.name_from, "Sender");
|
||||
const char *text = "hi";
|
||||
whisper.size = static_cast<uint16_t>(sizeof(whisper) + std::strlen(text));
|
||||
std::vector<uint8_t> wire = raw(whisper);
|
||||
wire.insert(wire.end(), text, text + std::strlen(text));
|
||||
GCCharacterDel next{HDR_GC_CHARACTER_DEL, 77};
|
||||
auto next_wire = raw(next);
|
||||
wire.insert(wire.end(), next_wire.begin(), next_wire.end());
|
||||
s.feed(wire.data(), sizeof(GCWhisperHead) / 2);
|
||||
CHECK(headers.empty(), "whisper partial fixed head waits");
|
||||
s.feed(wire.data() + sizeof(GCWhisperHead) / 2,
|
||||
wire.size() - sizeof(GCWhisperHead) / 2);
|
||||
CHECK(headers.size() == 2 && headers[0] == HDR_GC_WHISPER &&
|
||||
headers[1] == HDR_GC_CHARACTER_DEL,
|
||||
"whisper text tail does not swallow the following static packet");
|
||||
}
|
||||
|
||||
// The reference consumes HYBRIDCRYPT payloads in every phase and returns
|
||||
// without sending them through the normal GC parser. They must not become a
|
||||
// false unsupported-packet disconnect in the adapted client.
|
||||
{
|
||||
ClassicStream s;
|
||||
int callbacks = 0;
|
||||
int errors = 0;
|
||||
s.on_packet = [&](uint8_t, const uint8_t *, uint32_t) {
|
||||
++callbacks;
|
||||
return true;
|
||||
};
|
||||
s.on_error = [&](const std::string &) { ++errors; };
|
||||
DynHead key{HDR_GC_HYBRIDCRYPT_KEYS, static_cast<uint16_t>(sizeof(DynHead))};
|
||||
auto b = raw(key);
|
||||
s.feed(b.data(), b.size());
|
||||
CHECK(callbacks == 0 && errors == 0,
|
||||
"HYBRIDCRYPT_KEYS is consumed without normal parser callback");
|
||||
}
|
||||
|
||||
// A phase packet returns from the current reference phase function. Bytes
|
||||
// already buffered behind it stay for the next phase tick.
|
||||
{
|
||||
ClassicStream s;
|
||||
int phases = 0;
|
||||
int packets = 0;
|
||||
s.on_phase = [&](uint8_t) { ++phases; };
|
||||
s.on_packet = [&](uint8_t, const uint8_t *, uint32_t) {
|
||||
++packets;
|
||||
return true;
|
||||
};
|
||||
Phase_ phase{HDR_GC_PHASE, PHASE_GAME};
|
||||
GCCharacterDel next{HDR_GC_CHARACTER_DEL, 42};
|
||||
std::vector<uint8_t> burst = raw(phase);
|
||||
auto next_bytes = raw(next);
|
||||
burst.insert(burst.end(), next_bytes.begin(), next_bytes.end());
|
||||
s.feed(burst.data(), burst.size());
|
||||
CHECK(phases == 1 && packets == 0,
|
||||
"phase boundary stops dispatch before the pipelined next packet");
|
||||
s.feed(nullptr, 0);
|
||||
CHECK(packets == 1,
|
||||
"pipelined packet is dispatched on the next phase tick");
|
||||
}
|
||||
|
||||
// GamePhase's bounded pump stops after the third packet while the receive
|
||||
// buffer is below 8192 bytes. The fourth packet remains for the next tick.
|
||||
{
|
||||
ClassicStream s;
|
||||
int packets = 0;
|
||||
s.on_packet = [&](uint8_t, const uint8_t *, uint32_t) {
|
||||
++packets;
|
||||
return true;
|
||||
};
|
||||
Phase_ phase{HDR_GC_PHASE, PHASE_GAME};
|
||||
s.feed(&phase, sizeof(phase));
|
||||
GCCharacterDel del{HDR_GC_CHARACTER_DEL, 100};
|
||||
std::vector<uint8_t> burst;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
auto bytes = raw(del);
|
||||
bytes[1] = static_cast<uint8_t>(100 + i);
|
||||
burst.insert(burst.end(), bytes.begin(), bytes.end());
|
||||
}
|
||||
s.feed(burst.data(), burst.size());
|
||||
CHECK(packets == 3, "GamePhase bounded pump consumes three small packets");
|
||||
s.feed(nullptr, 0);
|
||||
CHECK(packets == 4, "GamePhase bounded pump resumes on the next tick");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------- send seq sequencing
|
||||
{
|
||||
ClassicStream s;
|
||||
@@ -234,6 +339,28 @@ int main() {
|
||||
CHECK(hits == 1, "partial: delivered once the rest arrives");
|
||||
}
|
||||
|
||||
// CheckPacket strips only a leading run of zero padding. A zero-only read
|
||||
// is incomplete and must not be reported as an unknown packet.
|
||||
{
|
||||
ClassicStream s;
|
||||
int hits = 0;
|
||||
std::string err;
|
||||
s.on_packet = [&](uint8_t, const uint8_t *, uint32_t) {
|
||||
++hits;
|
||||
return true;
|
||||
};
|
||||
s.on_error = [&](const std::string &e) { err = e; };
|
||||
const uint8_t padding[] = {0, 0};
|
||||
s.feed(padding, sizeof(padding));
|
||||
CHECK(hits == 0 && err.empty(), "zero-only leading padding waits without error");
|
||||
GCCharacterDel del{HDR_GC_CHARACTER_DEL, 100};
|
||||
auto b = raw(del);
|
||||
std::vector<uint8_t> padded = {0, 0};
|
||||
padded.insert(padded.end(), b.begin(), b.end());
|
||||
s.feed(padded.data(), padded.size());
|
||||
CHECK(hits == 1 && err.empty(), "leading zero run is removed before valid packet");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------- unknown header
|
||||
{
|
||||
ClassicStream s;
|
||||
@@ -294,13 +421,21 @@ int main() {
|
||||
ClassicStream s;
|
||||
std::string err;
|
||||
s.on_error = [&](const std::string &e) { err = e; };
|
||||
s.on_packet = [&](uint8_t, const uint8_t *, uint32_t) { return false; };
|
||||
int calls = 0;
|
||||
s.on_packet = [&](uint8_t, const uint8_t *, uint32_t) {
|
||||
++calls;
|
||||
return calls > 1;
|
||||
};
|
||||
GCCharacterDel del{HDR_GC_CHARACTER_DEL, 99};
|
||||
auto b = raw(del);
|
||||
s.feed(b.data(), b.size());
|
||||
std::vector<uint8_t> glued = b;
|
||||
glued.insert(glued.end(), b.begin(), b.end());
|
||||
s.feed(glued.data(), glued.size());
|
||||
CHECK(err.find("packet handler rejected") != std::string::npos,
|
||||
"rejected packet -> explicit on_error");
|
||||
CHECK(s.state() == ClassicStream::State::Offline, "rejected packet -> disconnect");
|
||||
CHECK(calls == 1, "rejected packet clears pipelined receive bytes");
|
||||
s.feed(b.data(), b.size());
|
||||
CHECK(calls == 2, "stream owner survives known handler failure");
|
||||
}
|
||||
|
||||
// ---- GC_KEY_AGREEMENT: a partial packet just waits (no error, no crash) ----
|
||||
@@ -315,6 +450,23 @@ int main() {
|
||||
CHECK(err.empty(), "partial GC_KEY_AGREEMENT -> buffered, no error");
|
||||
}
|
||||
|
||||
// ------------------------------------------------ non-blocking connect gate
|
||||
{
|
||||
// TEST-NET-1 gives us a deterministic unroutable destination on normal
|
||||
// CI hosts. If the environment rejects it synchronously, keep the rest of
|
||||
// the socket-free suite usable; when it is pending, limit_ms=0 proves that
|
||||
// Connecting is not promoted to Online before writability/timeout.
|
||||
ClassicStream s;
|
||||
if (s.connect("192.0.2.1", 9, 0) && s.state() == ClassicStream::State::Connecting) {
|
||||
s.process();
|
||||
CHECK(s.state() == ClassicStream::State::Offline,
|
||||
"pending classic connect times out before Online");
|
||||
CHECK(s.last_error() == "connect timeout", "pending classic connect reports timeout");
|
||||
} else {
|
||||
std::puts("classic connect timeout probe skipped (connect was synchronous)");
|
||||
}
|
||||
}
|
||||
|
||||
if (g_fail) {
|
||||
std::fprintf(stderr, "%d check(s) failed\n", g_fail);
|
||||
return 1;
|
||||
|
||||
Reference in New Issue
Block a user