Skip to content

Conversation

@Hdt80bro
Copy link

@Hdt80bro Hdt80bro commented Nov 6, 2025

The time requirement for ranking custom games is in place so that connection issues can be discovered early in the match and rehosted without penalty to rating. However, if a team unanimously agrees to recall during that time frame, it is safe to presume that it was not due to any connection issues and they actually do want to forfeit the match.

This PR introduces a check on the prevalidation step of ranking a game that skips the TOO_SHORT path if there wasn't anything that looks like it could have been a connection issue by introducing a RECALL army result.

Note that the game itself will still need to be updated to report such a result; this is only the preparation step to allow the server to handle recalling.

Summary by CodeRabbit

  • New Features

    • Added a RECALL outcome to represent early recalls/withdrawals and included its resolution behavior.
  • Bug Fixes

    • Pre-rate validity now considers per-team outcome indicators together with elapsed time before marking games as too short, reducing false invalidations for early disconnect/recall scenarios.
  • Tests

    • Added unit coverage for early-recall scenarios and refactored outcome-resolution tests to exercise all outcome permutations.

@coderabbitai
Copy link

coderabbitai bot commented Nov 6, 2025

Walkthrough

Pre-rate validity checks now accept per-team army outcomes; RECALL was added to outcome enums. CustomGame infers possible connection issues from team outcomes and only marks games TOO_SHORT when both short duration and quitting-like outcomes (including RECALL) are present. Tests and helpers updated accordingly.

Changes

Cohort / File(s) Change Summary
Enum Extension
server/games/game_results.py
Added RECALL to ArmyOutcome and ArmyReportedOutcome; mapped ArmyReportedOutcome.RECALL to resolve to ArmyOutcome.RECALL.
Signature & Call Site
server/games/game.py
_run_pre_rate_validity_checks signature updated to accept team_army_results: list[set[ArmyOutcome]]; call site in resolve_game_results updated to pass team_player_partial_outcomes.
Validity Logic
server/games/custom_game.py
Imported ArmyOutcome; _run_pre_rate_validity_checks(self, team_army_outcomes: list[set[ArmyOutcome]]) now computes duration, inspects team outcomes against a quitting-like set (DEFEAT, UNKNOWN, CONFLICTING, RECALL), and marks ValidityState.TOO_SHORT only when short duration and potential connection issues coincide; added explanatory comments.
Tests & Helpers
tests/unit_tests/test_custom_game.py, tests/unit_tests/test_game_results.py, tests/unit_tests/test_game_resolution.py
Added test_rate_game_early_recall; added assertion for ArmyReportedOutcome.RECALL.to_resolved(); introduced ResolutionTest helper and refactored outcome tests into a grid-driven approach.

Sequence Diagram(s)

sequenceDiagram
    participant Game as Game (server/games/game.py)
    participant Custom as CustomGame (server/games/custom_game.py)

    Game->>Game: build team_player_partial_outcomes
    Game->>Custom: _run_pre_rate_validity_checks(team_player_partial_outcomes)
    activate Custom
    Custom->>Custom: duration = finished_at - launched_at
    Note right of Custom `#F0F8FF`: looks_like_quitting = {DEFEAT, UNKNOWN, CONFLICTING, RECALL}
    Custom->>Custom: analyze team_army_outcomes for quitting-like patterns
    alt short duration AND potential connection issues
        Custom->>Custom: mark_invalid(ValidityState.TOO_SHORT)
    else
        Custom->>Custom: proceed with normal validation
    end
    deactivate Custom
    Custom-->>Game: return validity decision
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review focus:
    • server/games/custom_game.py — duration computation, quitting-set inclusion of RECALL, and correct use of mark_invalid.
    • server/games/game.py — ensure team_player_partial_outcomes is computed before calling the updated validity check.
    • Tests — verify test_rate_game_early_recall backdating and ResolutionTest behavior.

"I hopped through code with nimble feet,
I gave RECALL a tiny seat.
Short games checked by time and sign,
Rabbity logic says the rules align.
🐇✨"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main change: waiving the time requirement for ranking custom games when recalling, which directly corresponds to the core functionality added in the PR.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e203cd and e573963.

📒 Files selected for processing (6)
  • server/games/custom_game.py (2 hunks)
  • server/games/game.py (2 hunks)
  • server/games/game_results.py (2 hunks)
  • tests/unit_tests/test_custom_game.py (1 hunks)
  • tests/unit_tests/test_game_resolution.py (1 hunks)
  • tests/unit_tests/test_game_results.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
tests/unit_tests/test_game_results.py (1)
server/games/game_results.py (3)
  • ArmyReportedOutcome (27-46)
  • to_resolved (38-46)
  • ArmyOutcome (15-24)
server/games/game.py (2)
server/games/custom_game.py (1)
  • _run_pre_rate_validity_checks (22-43)
server/games/game_results.py (1)
  • ArmyOutcome (15-24)
tests/unit_tests/test_game_resolution.py (2)
server/games/game_results.py (3)
  • ArmyOutcome (15-24)
  • GameResolutionError (237-238)
  • resolve_game (241-305)
server/db/typedefs.py (1)
  • GameOutcome (17-21)
tests/unit_tests/test_custom_game.py (5)
server/games/custom_game.py (1)
  • CustomGame (10-43)
tests/conftest.py (1)
  • players (298-303)
server/games/typedefs.py (2)
  • GameState (10-14)
  • ValidityState (48-74)
server/games/game.py (5)
  • players (212-223)
  • set_player_option (616-621)
  • launch (725-746)
  • add_result (317-359)
  • on_game_finish (467-488)
tests/unit_tests/conftest.py (1)
  • add_connected_players (168-179)
server/games/custom_game.py (2)
server/games/game_results.py (2)
  • ArmyOutcome (15-24)
  • outcome (105-117)
server/games/game.py (2)
  • _run_pre_rate_validity_checks (490-491)
  • players (212-223)
🪛 GitHub Actions: Lint
server/games/game_results.py

[error] 21-21: flake8: E261 at least two spaces before inline comment.

server/games/game.py

[warning] 519-519: flake8: W293 blank line contains whitespace.

server/games/custom_game.py

[error] 37-37: Need type annotation for "all_outcomes" (hint: "all_outcomes: dict[, ] = ...") [var-annotated]


[error] 39-39: Argument 1 to "ior" of "dict" has incompatible type "set[ArmyOutcome]"; expected "Iterable[tuple[Any, Any]]" [arg-type]


[error] 40-40: Unsupported left operand type for & ("dict[Any, Any]") [operator]

🪛 GitHub Check: flake8
server/games/game_results.py

[failure] 21-21:
at least two spaces before inline comment

server/games/game.py

[warning] 519-519:
blank line contains whitespace

tests/unit_tests/test_game_resolution.py

[failure] 49-49:
multiple spaces after ','


[failure] 49-49:
multiple spaces after ','


[failure] 48-48:
whitespace before ']'


[failure] 48-48:
multiple spaces after ','


[failure] 48-48:
multiple spaces after ','


[failure] 48-48:
multiple spaces after ','


[failure] 48-48:
multiple spaces after ','


[failure] 48-48:
multiple spaces after ','

tests/unit_tests/test_custom_game.py

[failure] 16-16:
too many blank lines (3)

🪛 GitHub Check: mypy
server/games/custom_game.py

[failure] 40-40:
Unsupported left operand type for & ("dict[Any, Any]") [operator]


[failure] 39-39:
Argument 1 to "ior" of "dict" has incompatible type "set[ArmyOutcome]"; expected "Iterable[tuple[Any, Any]]" [arg-type]


[failure] 37-37:
Need type annotation for "all_outcomes" (hint: "all_outcomes: dict[, ] = ...") [var-annotated]

🪛 Ruff (0.14.3)
tests/unit_tests/test_game_resolution.py

49-49: Indexed access to type list uses type tuple instead of an integer or slice

(RUF016)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: unit-test
🔇 Additional comments (8)
server/games/game_results.py (1)

33-33: LGTM!

The RECALL variant is correctly added and will map to ArmyOutcome.RECALL through the to_resolved() method.

tests/unit_tests/test_game_results.py (1)

22-22: LGTM!

The test correctly verifies that ArmyReportedOutcome.RECALL resolves to ArmyOutcome.RECALL.

tests/unit_tests/test_custom_game.py (1)

16-36: LGTM!

The test correctly validates that an early recall (within the grace period) results in a VALID game state, which aligns with the PR objective of waiving the time requirement for ranking when there is evidence of unanimous recall.

server/games/game.py (2)

490-491: LGTM!

The signature update correctly accepts team army results for enhanced pre-rate validation checks.


515-520: LGTM!

The logic correctly constructs team_player_partial_outcomes and passes it to the validity checks, enabling outcome-based validation.

server/games/custom_game.py (3)

22-23: LGTM!

The signature correctly accepts team_army_outcomes parameter for outcome-based validation.


25-36: Excellent design for detecting connection issues.

The logic correctly identifies that only a unanimous recall indicates intentional forfeiture, while defeats could indicate connection problems. The grace period approach is sound.


42-43: LGTM!

The conditional logic correctly applies the TOO_SHORT validity state only when connection issues are suspected and the game is within the grace period.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e573963 and d0a8e57.

📒 Files selected for processing (4)
  • server/games/game.py (2 hunks)
  • server/games/game_results.py (2 hunks)
  • tests/unit_tests/test_custom_game.py (1 hunks)
  • tests/unit_tests/test_game_resolution.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit_tests/test_custom_game.py
🧰 Additional context used
🧬 Code graph analysis (2)
tests/unit_tests/test_game_resolution.py (2)
server/db/typedefs.py (1)
  • GameOutcome (17-21)
server/games/game_results.py (3)
  • ArmyOutcome (15-24)
  • GameResolutionError (237-238)
  • resolve_game (241-305)
server/games/game.py (2)
server/games/custom_game.py (1)
  • _run_pre_rate_validity_checks (22-43)
server/games/game_results.py (1)
  • ArmyOutcome (15-24)
🪛 GitHub Actions: Lint
tests/unit_tests/test_game_resolution.py

[error] 10-10: flake8: E302 expected 2 blank lines, found 1.

🪛 GitHub Actions: Test
tests/unit_tests/test_game_resolution.py

[error] 10-11: NameError: name 'Optional' is not defined during test collection. Ensure 'Optional' is imported from typing (e.g., from typing import Optional).

🪛 GitHub Check: Codacy Static Code Analysis
tests/unit_tests/test_game_resolution.py

[warning] 11-11: tests/unit_tests/test_game_resolution.py#L11
undefined name 'Optional' (F821)


[warning] 13-13: tests/unit_tests/test_game_resolution.py#L13
undefined name 'Optional' (F821)

🪛 GitHub Check: flake8
tests/unit_tests/test_game_resolution.py

[failure] 23-23:
expected 2 blank lines after class or function definition, found 1


[failure] 10-10:
expected 2 blank lines, found 1

🪛 Ruff (0.14.3)
tests/unit_tests/test_game_resolution.py

11-11: Undefined name Optional

(F821)


13-13: Undefined name Optional

(F821)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (3)
server/games/game_results.py (1)

21-21: LGTM! Clean enum extension for RECALL outcome.

The RECALL additions to both enums integrate seamlessly with the existing resolution flow via to_resolved().

Also applies to: 33-33

server/games/game.py (1)

490-491: LGTM! Method signature correctly threaded to enable outcome-aware validity checks.

The updated signature accepts per-team army outcomes, allowing subclasses (like CustomGame) to implement recall-aware connection issue detection.

Also applies to: 520-521

tests/unit_tests/test_game_resolution.py (1)

10-26: Clean refactor using test wrappers to centralize assertion logic.

The ResolutionTest wrapper class eliminates repetitive assertion patterns and improves test readability. The grid-based approach systematically covers all 36 outcome permutations.

Also applies to: 38-71

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/unit_tests/test_game_resolution.py (2)

62-63: Clarify the column order comment.

The two-line comment layout is confusing and doesn't clearly convey the column order. A single-line comment would be clearer.

Apply this diff:

-    #        Victory     Recall      Unknown
-    #              Defeat      Draw        Conflicting
+    # Columns: Victory, Defeat, Recall, Draw, Unknown, Conflicting
     grid = [[ERR_, WIN_, WIN_, WIN_, WIN_, WIN_],  # Victory

73-73: Fix variable name to follow PEP 8.

Variable names should be lowercase. Resolution should be resolution.

Apply this diff:

-        for outcome2, Resolution in enumerate(row):
+        for outcome2, resolution in enumerate(row):
             team_outcomes = [{outcome_list[outcome1]}, {outcome_list[outcome2]}]
-            Resolution(team_outcomes)
+            resolution(team_outcomes)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 315e835 and d971144.

📒 Files selected for processing (1)
  • tests/unit_tests/test_game_resolution.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/unit_tests/test_game_resolution.py (2)
server/db/typedefs.py (1)
  • GameOutcome (17-21)
server/games/game_results.py (3)
  • ArmyOutcome (15-24)
  • GameResolutionError (237-238)
  • resolve_game (241-305)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: unit-test
🔇 Additional comments (3)
tests/unit_tests/test_game_resolution.py (3)

13-25: Well-designed test helper class.

The ResolutionTest class provides a clean abstraction that centralizes resolution testing logic, making the test cases more readable and maintainable. The use of __call__ allows instances to be invoked directly, which is elegant.


64-72: Comprehensive grid test correctly validates all outcome combinations.

The grid accurately models the resolution logic for all 36 possible 1v1 outcome pairs. The values correctly treat RECALL the same as DEFEAT for resolution purposes, which aligns with the PR objectives. The use of enumerate properly accesses indices for outcome_list.

Also applies to: 74-75


42-43: Excellent refactoring of test assertions.

The conversion from direct pytest.raises calls and assertions to the ResolutionTest instances makes the tests more concise and consistent while preserving their behavior. This improves maintainability.

Also applies to: 49-49, 83-83, 91-91, 99-99, 107-107, 115-115, 123-123

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/unit_tests/test_game_resolution.py (1)

52-73: Consider improving comment formatting for clarity.

The grid logic correctly tests all 36 outcome combinations, and the RECALL row properly matches the DEFEAT row as expected. However, the column header comment (lines 59-60) has confusing staggered formatting.

Consider applying this diff for clearer column alignment:

-    err_, win_, draw, loss = (resolve_to_error, resolve_to_win, resolve_to_draw, resolve_to_loss)
-    #        Victory     Recall      Unknown
-    #              Defeat      Draw        Conflicting
-    grid = [[err_, win_, win_, win_, win_, win_],  # Victory
+    err = resolve_to_error
+    win = resolve_to_win
+    draw = resolve_to_draw
+    loss = resolve_to_loss
+    # Columns: Victory, Defeat, Recall, Draw, Unknown, Conflicting
+    grid = [[err, win, win, win, win, win],  # Victory
-            [loss, draw, draw, err_, err_, err_],  # Defeat
-            [loss, draw, draw, err_, err_, err_],  # Recall
-            [loss, err_, err_, draw, err_, err_],  # Draw
-            [loss, err_, err_, err_, err_, err_],  # Unknown
-            [loss, err_, err_, err_, err_, err_]]  # Conflicting
+            [loss, draw, draw, err, err, err],  # Defeat
+            [loss, draw, draw, err, err, err],  # Recall
+            [loss, err, err, draw, err, err],  # Draw
+            [loss, err, err, err, err, err],  # Unknown
+            [loss, err, err, err, err, err]]  # Conflicting
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d971144 and 8cc0aee.

📒 Files selected for processing (2)
  • server/games/custom_game.py (2 hunks)
  • tests/unit_tests/test_game_resolution.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
server/games/custom_game.py (2)
server/games/game_results.py (2)
  • ArmyOutcome (15-24)
  • outcome (105-117)
server/games/game.py (2)
  • _run_pre_rate_validity_checks (490-491)
  • players (212-223)
tests/unit_tests/test_game_resolution.py (2)
server/db/typedefs.py (1)
  • GameOutcome (17-21)
server/games/game_results.py (3)
  • ArmyOutcome (15-24)
  • GameResolutionError (237-238)
  • resolve_game (241-305)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: unit-test
🔇 Additional comments (3)
server/games/custom_game.py (1)

6-6: LGTM! Import and signature align with recall feature.

The import of ArmyOutcome and the updated method signature correctly support the new recall-aware validity checking logic.

Also applies to: 22-22

tests/unit_tests/test_game_resolution.py (2)

13-30: LGTM! Helper class effectively centralizes test logic.

The ResolutionTest class encapsulates resolution expectations and reduces duplication across tests. The snake_case instance names (resolve_to_error, resolve_to_win, etc.) clearly convey test intent.


33-121: LGTM! Tests correctly use the new helper instances.

All test functions properly leverage the ResolutionTest wrappers, maintaining existing behavior while improving readability.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
server/games/custom_game.py (2)

22-22: Parameter name inconsistency with base class.

The base class in game.py uses team_army_results while this override uses team_army_outcomes. Consider using consistent parameter naming across the inheritance hierarchy for clarity.

Apply this diff for consistency:

-    async def _run_pre_rate_validity_checks(self, team_army_outcomes: list[set[ArmyOutcome]]):
+    async def _run_pre_rate_validity_checks(self, team_army_results: list[set[ArmyOutcome]]):
         assert self.launched_at is not None
 
         # if there were connection issues, they are likely to show up early in
         # the match and manifest as players quitting out - in these cases, we
         # grant a grace period to custom games before we count them as ranked
         limit = len(self.players) * 60
         duration = time.time() - self.launched_at
 
         # As we only get extremely limited data (the army results), our lens of
         # what can look like "quitting out" is rather large (i.e. the player is
         # "defeated").
         # In other words, only if a team unanimously recalled would we know
         # there weren't any connection issues.
         looks_like_quitting = {ArmyOutcome.DEFEAT, ArmyOutcome.UNKNOWN, ArmyOutcome.CONFLICTING}
-        all_outcomes = {outcome for team in team_army_outcomes for outcome in team}
+        all_outcomes = {outcome for team in team_army_results for outcome in team}
         possible_conn_issues = len(all_outcomes & looks_like_quitting) > 0
 
         if not self.enforce_rating and possible_conn_issues and duration < limit:

25-40: Logic correctly implements recall exemption from TOO_SHORT validation.

The implementation properly achieves the PR objective by:

  • Excluding RECALL from looks_like_quitting, treating it as an intentional forfeit
  • Only inferring connection issues when outcomes include DEFEAT, UNKNOWN, or CONFLICTING
  • Requiring all three conditions (non-enforced rating, possible connection issues, short duration) before marking TOO_SHORT

This means unanimous recalls bypass the time requirement as intended, while still catching likely disconnects.

Optional: Simplify boolean check on line 38.

-        possible_conn_issues = len(all_outcomes & looks_like_quitting) > 0
+        possible_conn_issues = bool(all_outcomes & looks_like_quitting)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d90f6a3 and 3bcd3d2.

📒 Files selected for processing (1)
  • server/games/custom_game.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
server/games/custom_game.py (2)
server/games/game_results.py (2)
  • ArmyOutcome (15-24)
  • outcome (105-117)
server/games/game.py (2)
  • _run_pre_rate_validity_checks (490-491)
  • players (212-223)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: unit-test
🔇 Additional comments (1)
server/games/custom_game.py (1)

6-6: LGTM! Import is correct and necessary.

The ArmyOutcome import is properly used in the method signature and logic to distinguish intentional recalls from potential connection issues.

@Gatsik
Copy link
Contributor

Gatsik commented Nov 11, 2025

Why all this hassle when EnforceRating exists and is checked here (not self.enforce_rating)?

Wouldn't it be way less work to just enforce rating on recall like it is done when ACU is killed? And it won't require any server changes at all

@Hdt80bro
Copy link
Author

Hdt80bro commented Nov 11, 2025

Hmm, I didn't know the game code was in charge of that - I thought the server set that property. That might be easier. My question is then, from a design stand-point, should the game or the server ultimately be in charge of if a game is ranked? It seems like it fundamentally shouldn't be part of the game, but maybe the ease-of-use you're suggesting would make it preferable nonetheless.

It should be noted that, at this point, if effort is the limiting factor, then all of the work to make this happen has already been done.

@Gatsik
Copy link
Contributor

Gatsik commented Nov 12, 2025

I guess I just chose words poorly. By 'less work' I mean 'less complex' or 'less changes/diff', and that often does not necessarily mean that there were 'less work/effort' done. In contrast, it often means that more effort were put into research to understand the problem better.
And 'less complex' is obviously better than 'more complex' with less potential problems -- and it looks like it's possible to avoid server changes entirely!

It seems that EnforceRating exists purely to bypass server's 'unranked' time limit (when the game is counted as TOO_SHORT), and your situation with recall is exactly that!

@Hdt80bro
Copy link
Author

We'll go ahead with the game-centered approach using EnforceRating.

@Hdt80bro Hdt80bro closed this Nov 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants