fix(handlers): guard GetBoostTimeLimit against past boost_time

Live-server testing via protbot surfaced an inconsistency between
GetBoostTimeLimit and GetBoostRight: on the same character, the
former reported a far-future boost limit (2288912640 = year 2042)
while the latter correctly reported "expired / available". The two
handlers read the same boost_time row and disagreed.

Root cause: GetBoostTimeLimit was doing a naked uint32(int64) cast
on boostLimit.Unix(). The test character's boost_time was actually
year 1906 (a pre-1970 sentinel left behind by the pre-#187 bug),
whose negative int64 Unix timestamp wraps through uint32 to a huge
positive value the client interprets as a permanently active boost.

Harmonise the guard with GetBoostRight: return 0 whenever the stored
boost_time is not strictly after TimeAdjusted(), covering both the
pre-1970 wraparound and the "already expired" case.

Add a healing migration (0011_fix_stale_boost_time) that NULLs out
any boost_time column older than 1970 or more than 10 years in the
future, so affected characters recover on upgrade without waiting
for a fresh boost start.

Regression test uses the exact year-1906 value observed on the live
frontier.mogapedia.fr test account.
This commit is contained in:
Houmgaor
2026-04-06 18:26:15 +02:00
parent e2b0a8ad8c
commit 4ad0012f62
3 changed files with 49 additions and 3 deletions

View File

@@ -617,6 +617,29 @@ func TestHandleMsgMhfGetBoostTimeLimit_DisableBoostTime(t *testing.T) {
}
}
// Regression: GetBoostTimeLimit must return 0 for a stale past boost_time
// (e.g. a pre-1970 sentinel left over from the pre-#187 bug). Without the
// guard, uint32(negative int64) wraps to a huge future-looking timestamp
// the client interprets as a permanently active boost — while
// GetBoostRight correctly reports it as expired, producing an observable
// inconsistency between the two handlers.
func TestHandleMsgMhfGetBoostTimeLimit_PastBoostTime(t *testing.T) {
server := createMockServer()
charMock := newMockCharacterRepo()
// Year 1906 — the actual value found on the live test server.
charMock.times["boost_time"] = time.Date(1906, 1, 1, 0, 0, 0, 0, time.UTC)
server.charRepo = charMock
session := createMockSession(1, server)
handleMsgMhfGetBoostTimeLimit(session, &mhfpacket.MsgMhfGetBoostTimeLimit{AckHandle: 100})
p := <-session.sendPackets
payload := ackBufPayload(t, p.data)
if len(payload) != 4 || payload[0] != 0 || payload[1] != 0 || payload[2] != 0 || payload[3] != 0 {
t.Errorf("expected zero uint32 payload for past boost_time, got %x", payload)
}
}
// Regression for #187: GetBoostRight must report "no right" when disabled.
func TestHandleMsgMhfGetBoostRight_DisableBoostTime(t *testing.T) {
server := createMockServer()