fix(multiplayer): reject fractional ranked games

This commit is contained in:
Josh Creek
2026-09-01 22:36:05 +01:00
parent a10c6d47f9
commit 03bc723b70
3 changed files with 12 additions and 1 deletions
+9 -1
View File
@@ -29,7 +29,7 @@ func apply(payload: Dictionary) -> bool:
var next_volatility := float(payload["volatility"])
var next_games := int(payload["ranked_games"])
var next_tier := String(payload["tier"])
if not is_finite(next_rating) or not is_finite(next_rd) or not is_finite(next_volatility) or next_rating < 0.0 or next_rd < 0.0 or next_volatility < 0.0 or next_games < 0 or next_tier.is_empty():
if not is_finite(next_rating) or not is_finite(next_rd) or not is_finite(next_volatility) or not _valid_nonnegative_integer(payload["ranked_games"]) or next_rating < 0.0 or next_rd < 0.0 or next_volatility < 0.0 or next_games < 0 or next_tier.is_empty():
return _reject("Profile response contains invalid values")
rating = next_rating
rd = next_rd
@@ -69,6 +69,14 @@ static func is_valid_opaque_id(value: String) -> bool:
return id_pattern.search(value) != null
static func _valid_nonnegative_integer(value: Variant) -> bool:
if value is int:
return int(value) >= 0
if value is float:
return is_finite(float(value)) and float(value) >= 0.0 and float(value) == floor(float(value))
return false
func set_error(reason: String) -> void:
available = false
error_message = reason
@@ -156,6 +156,7 @@ func test_ranked_profile_is_backend_display_data_and_rejects_unsafe_values() ->
assert_true(not profile.available, "unsafe response is not displayed")
assert_true(not profile.apply({"rating": 1500.0, "rd": 200.0, "volatility": 0.06, "ranked_games": 3, "tier": "", "provisional": false}), "empty tier is rejected")
assert_true(not profile.apply({"rating": 1500.0, "rd": 200.0, "volatility": 0.06, "ranked_games": 3, "tier": "GOLD", "provisional": "false"}), "string boolean is rejected")
assert_true(not profile.apply({"rating": 1500.0, "rd": 200.0, "volatility": 0.06, "ranked_games": 3.5, "tier": "GOLD", "provisional": false}), "fractional ranked games is rejected")
func test_ranked_profile_projects_and_bounds_season_countdown() -> void:
+2
View File
@@ -1583,6 +1583,8 @@ Ranked profile `season_id` now enforces the OpenAPI opaque-ID shape and exact st
Persisted matchmaking snapshots now validate field types, non-negative integral revisions/epochs, and proposal identity/state consistency before restoration; malformed restart data cannot be coerced into an active projection.
Ranked profile projection now rejects fractional `ranked_games` values instead of silently truncating them, matching the OpenAPI integer contract.
Signed MatchNet claims now also require exact JSON string/integer types for every identity, protocol, expiry, slot, team, and generation field; string-number coercion is rejected before canonical signature verification.
Presentation progress: a shared `Game/themes/cosmic_clash_theme.tres` now gives the menu, lobby, matchmaking, and settings surfaces consistent button, input, option, and label styling. The custom-font portion of `TODO.md` remains open until a distributable font asset is selected.