feat: display backend ranked profile

This commit is contained in:
Josh Creek
2026-08-31 22:44:21 +01:00
parent 62c1478913
commit fddbebb33b
6 changed files with 118 additions and 4 deletions
+19 -3
View File
@@ -13,6 +13,7 @@ const PERSIST_PATH := "user://matchmaking_state.cfg"
var base_url := DEFAULT_BASE_URL
var access_token := ""
var state: MatchmakingState
var ranked_profile: RankedProfileState
var _request: HTTPRequest
var _operation := ""
@@ -20,6 +21,7 @@ var _operation := ""
func _ready() -> void:
state = MatchmakingState.new()
ranked_profile = RankedProfileState.new()
_load_persisted_state()
state.changed.connect(_persist_state)
_request = HTTPRequest.new()
@@ -61,6 +63,10 @@ func recover_proposal(proposal_id: String) -> Error:
return _start_request("proposal_recover", HTTPClient.METHOD_GET, "/v1/proposals/" + proposal_id, {}, "")
func fetch_ranked_profile() -> Error:
return _start_request("ranked_profile", HTTPClient.METHOD_GET, "/v1/profile/ranked", {}, "")
func heartbeat(ticket_id: String, expected_revision: int) -> Error:
if ticket_id.is_empty() or expected_revision < 0:
return ERR_INVALID_PARAMETER
@@ -114,7 +120,9 @@ func _on_request_completed(result: HTTPRequest.Result, response_code: int, _head
var operation := _operation
_operation = ""
if result != HTTPRequest.RESULT_SUCCESS:
if operation == "queue_create" or operation == "queue_recover" or operation == "proposal_recover":
if operation == "ranked_profile":
ranked_profile.set_error("Ranked profile request failed")
elif operation == "queue_create" or operation == "queue_recover" or operation == "proposal_recover":
state.fail("Control-plane request failed")
else:
state.set_notice("Control-plane request failed; retrying is safe")
@@ -122,7 +130,9 @@ func _on_request_completed(result: HTTPRequest.Result, response_code: int, _head
return
var parsed = JSON.parse_string(body.get_string_from_utf8())
if not parsed is Dictionary:
if operation == "queue_create" or operation == "queue_recover" or operation == "proposal_recover":
if operation == "ranked_profile":
ranked_profile.set_error("Ranked profile returned invalid JSON")
elif operation == "queue_create" or operation == "queue_recover" or operation == "proposal_recover":
state.fail("Control-plane returned invalid JSON")
else:
state.set_notice("Control-plane returned invalid JSON; retrying is safe")
@@ -130,7 +140,9 @@ func _on_request_completed(result: HTTPRequest.Result, response_code: int, _head
return
if response_code < 200 or response_code >= 300:
var detail := String(parsed.get("error", "request rejected"))
if operation == "queue_create" or operation == "queue_recover" or operation == "proposal_recover":
if operation == "ranked_profile":
ranked_profile.set_error(detail)
elif operation == "queue_create" or operation == "queue_recover" or operation == "proposal_recover":
state.fail(detail)
else:
state.set_notice(detail)
@@ -143,6 +155,10 @@ func _on_request_completed(result: HTTPRequest.Result, response_code: int, _head
state.apply_ticket_update(normalize_ticket(payload))
elif operation.begins_with("proposal_"):
state.apply_proposal_update(payload)
elif operation == "ranked_profile":
if not ranked_profile.apply(payload):
request_failed.emit(operation, response_code, ranked_profile.error_message)
return
request_succeeded.emit(operation, payload)
+19
View File
@@ -8,6 +8,7 @@ const RECOVERY_POLL_SECONDS := 2.0
@onready var playlist_dropdown: OptionButton = %PlaylistDropdown
@onready var status_label: Label = %StatusLabel
@onready var detail_label: Label = %DetailLabel
@onready var ranked_profile_label: Label = %RankedProfileLabel
@onready var queue_button: Button = %QueueButton
@onready var cancel_button: Button = %CancelButton
@onready var accept_button: Button = %AcceptButton
@@ -24,9 +25,11 @@ func _ready() -> void:
playlist_dropdown.set_item_metadata(0, "casual")
playlist_dropdown.add_item("Ranked")
playlist_dropdown.set_item_metadata(1, "ranked")
playlist_dropdown.item_selected.connect(_on_playlist_selected)
ControlPlaneClient.state.changed.connect(_on_state_changed)
ControlPlaneClient.request_failed.connect(_on_request_failed)
ControlPlaneClient.request_succeeded.connect(_on_request_succeeded)
_refresh_ranked_profile()
_render(ControlPlaneClient.state.snapshot())
@@ -81,6 +84,20 @@ func _on_decline_pressed() -> void:
_on_local_error("Could not decline proposal: %s" % error_string(err))
func _on_playlist_selected(_index: int) -> void:
_refresh_ranked_profile()
func _refresh_ranked_profile() -> void:
var ranked := String(playlist_dropdown.get_selected_metadata()) == "ranked"
ranked_profile_label.visible = ranked
if not ranked:
return
var err := ControlPlaneClient.fetch_ranked_profile()
if err != OK and err != ERR_BUSY:
ranked_profile_label.text = "Ranked profile unavailable: %s" % error_string(err)
func _on_back_pressed() -> void:
if ControlPlaneClient.state.can_cancel():
status_label.text = "Cancel the active search before leaving"
@@ -93,11 +110,13 @@ func _on_state_changed(snapshot: Dictionary) -> void:
func _on_request_succeeded(_operation: String, _payload: Dictionary) -> void:
ranked_profile_label.text = ControlPlaneClient.ranked_profile.display_text()
_render(ControlPlaneClient.state.snapshot())
func _on_request_failed(_operation: String, _http_code: int, detail: String) -> void:
detail_label.text = detail
ranked_profile_label.text = ControlPlaneClient.ranked_profile.display_text()
_render(ControlPlaneClient.state.snapshot())
+60
View File
@@ -0,0 +1,60 @@
class_name RankedProfileState
extends RefCounted
# Read-only server projection. The client deliberately stores no tier bands
# or rating formula: it displays the backend's committed view verbatim after
# validating the shape and numeric safety of the response.
var available := false
var rating := 0.0
var rd := 0.0
var volatility := 0.0
var ranked_games := 0
var tier := ""
var provisional := false
var season_id := ""
var error_message := ""
func apply(payload: Dictionary) -> bool:
var required := ["rating", "rd", "volatility", "ranked_games", "tier", "provisional"]
for key in required:
if not payload.has(key):
return _reject("Profile response is missing " + key)
if not (payload["rating"] is int or payload["rating"] is float) or not (payload["rd"] is int or payload["rd"] is float) or not (payload["volatility"] is int or payload["volatility"] is float) or not payload["ranked_games"] is int or not payload["tier"] is String or not payload["provisional"] is bool:
return _reject("Profile response contains invalid types")
var next_rating := float(payload["rating"])
var next_rd := float(payload["rd"])
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():
return _reject("Profile response contains invalid values")
rating = next_rating
rd = next_rd
volatility = next_volatility
ranked_games = next_games
tier = next_tier
provisional = bool(payload["provisional"])
season_id = String(payload.get("season_id", ""))
available = true
error_message = ""
return true
func set_error(reason: String) -> void:
available = false
error_message = reason
func display_text() -> String:
if not available:
return error_message if not error_message.is_empty() else "Ranked profile unavailable"
var status := "Provisional" if provisional else tier
return "%s · %d ranked game%s" % [status, ranked_games, "" if ranked_games == 1 else "s"]
func _reject(reason: String) -> bool:
available = false
error_message = reason
return false