Unify counter clamp arithmetic into shared addClamped() helper (#7009)

* Unify counter clamp arithmetic into shared addClamped() helper

- Add addClamped() in new header clamped_arithmetic.h; uses a 64-bit
  intermediate so the addition cannot overflow int.
- Use it in Server_Card::incrementCounter() (clamps [0, MAX_COUNTERS_ON_CARD])
  and Server_Counter::incrementCount() (clamps [INT_MIN, INT_MAX]), removing
  the duplicated overflow-safe logic and its keep-in-sync TODO.
- Inline incrementCount() into server_counter.h; server_counter.cpp now holds
  only the constructor and getInfo().
- Clarify the card-counter bounds comment in trice_limits.h.

* Rename MAX_COUNTERS_ON_CARD to MAX_COUNTER_VALUE

The constant caps the counter's value, not how many counters can be on the card

* Add direct unit tests for addClamped() helper

* Harden offsetCardCounter() against signed-int overflow

Replace the raw oldValue + offset sum with addClamped(), clamping to [0, MAX_COUNTER_VALUE] without overflow.

* Comment update

* Remove class names from addClamped() docstring
This commit is contained in:
DawnFire42
2026-06-28 16:10:57 -07:00
committed by GitHub
parent fcac7493ad
commit 05ae6f47a6
11 changed files with 122 additions and 41 deletions
+1
View File
@@ -16,6 +16,7 @@ set(UTILITY_HEADERS
libcockatrice/utility/macros.h
libcockatrice/utility/passwordhasher.h
libcockatrice/utility/trice_limits.h
libcockatrice/utility/clamped_arithmetic.h
libcockatrice/utility/zone_names.h
libcockatrice/utility/days_years_between.h
)
@@ -0,0 +1,22 @@
#ifndef CLAMPED_ARITHMETIC_H
#define CLAMPED_ARITHMETIC_H
#include <QtGlobal>
#include <cstdint>
/**
* @brief Overflow-safe clamped addition: returns value + delta bounded to [minValue, maxValue].
*
* Uses a 64-bit intermediate so the addition cannot overflow int. Shared by the bounded
* counter arithmetic in both the client and the server.
*
* @note Requires minValue <= maxValue. Bounds come from trusted compile-time call sites;
* qBound() asserts this internally in debug builds.
*/
inline int addClamped(int value, int delta, int minValue, int maxValue)
{
const auto result = static_cast<int64_t>(value) + static_cast<int64_t>(delta);
return static_cast<int>(qBound(static_cast<int64_t>(minValue), result, static_cast<int64_t>(maxValue)));
}
#endif // CLAMPED_ARITHMETIC_H
@@ -1,6 +1,9 @@
#ifndef TRICE_LIMITS_H
#define TRICE_LIMITS_H
//! \todo Split trice_limits.h into focused single-purpose headers: string_limits.h,
//! dice_limits.h, counter_limits.h.
#include <QString>
// max size for short strings, like names and things that are generally a single phrase
@@ -15,11 +18,18 @@ constexpr uint MAXIMUM_DIE_SIDES = 1000000;
constexpr uint MINIMUM_DICE_TO_ROLL = 1;
constexpr uint MAXIMUM_DICE_TO_ROLL = 100;
// Card counter value bounds [0, MAX_COUNTERS_ON_CARD].
// Counters on cards (e.g., +1/+1 counters, charge counters) are non-negative physical game objects.
// The max of 999 is a display constraint (3-digit rendering) and reasonable gameplay limit.
// Server enforces these bounds; client may also check for UX optimization.
constexpr int MAX_COUNTERS_ON_CARD = 999;
/**
* @brief Upper bound for a bounded counter's value: [0, MAX_COUNTER_VALUE].
*
* Caps an individual counter's VALUE (e.g. a +1/+1 counter at 999), not how many counters
* something holds. Applies to counters that are constrained to a non-negative display range,
* such as card counters and commander tax. Unbounded counters (e.g. a player's life total)
* do not use this limit and may go negative, saturating only at the int range.
*
* The max of 999 is a display constraint (3-digit rendering) and a reasonable gameplay limit.
* The server enforces these bounds; the client may also check them for UX optimization.
*/
constexpr int MAX_COUNTER_VALUE = 999;
// optimized functions to get qstrings that are at most that long
static inline QString nameFromStdString(const std::string &_string)