diff --git a/server/src/Hokm.Server/Game/GameManager.cs b/server/src/Hokm.Server/Game/GameManager.cs
index c779c7e..164e84c 100644
--- a/server/src/Hokm.Server/Game/GameManager.cs
+++ b/server/src/Hokm.Server/Game/GameManager.cs
@@ -17,14 +17,15 @@ public sealed class Player
/// In-memory matchmaking + room registry. (EF/Postgres persistence is a TODO.)
public sealed partial class GameManager
{
- // Real players get priority. We re-check the queue every QueueWaitMs; the
- // moment a second human is waiting they're matched together (+ bots for any
- // empty seats), and a full group of 4 forms instantly. A player left ALONE
- // keeps waiting up to MaxAloneWaitMs so an online opponent has a genuine
- // chance to join before we fall back to a bot table. (QueueWaitMs mirrors
- // MATCH_QUEUE_WAIT_MS on the client — keep both in sync.)
+ // Real players get priority:
+ // • a full table of 4 humans forms instantly, at any time;
+ // • at the 15s checkpoint, if ≥2 humans are waiting they start together
+ // (bots fill any empty seats);
+ // • a player left ALONE keeps waiting until the 25s hard deadline, then we
+ // fill the seats with AI and start.
+ // (QueueWaitMs mirrors MATCH_QUEUE_WAIT_MS on the client — keep both in sync.)
private const int QueueWaitMs = 15000;
- private const int MaxAloneWaitMs = 75000;
+ private const int MaxAloneWaitMs = 25000;
private static readonly string[] BotNames =
{ "آرش", "کیان", "نیلوفر", "سارا", "رضا", "مهسا", "امیر", "پارسا", "الناز", "بابک" };
@@ -102,12 +103,14 @@ public sealed partial class GameManager
// bots fill any empty chairs (real players matched immediately).
if (_waiting.Count >= 2) { FormGroupLocked(_waiting.Count); return; }
- // Alone: keep the table open for an online opponent until the max wait
- // elapses, then fall back to a bot table. Re-arm the re-check timer.
+ // Alone: keep the table open for an online opponent until the 25s
+ // deadline, then fill the seats with AI. Re-arm the timer to land
+ // exactly on the deadline rather than overshooting by a full window.
var waited = (DateTime.UtcNow - _waiting[idx].since).TotalMilliseconds;
- if (waited < MaxAloneWaitMs)
+ var remaining = MaxAloneWaitMs - waited;
+ if (remaining > 250)
{
- _waiting[idx].timer.Change(QueueWaitMs, Timeout.Infinite);
+ _waiting[idx].timer.Change((int)remaining, Timeout.Infinite);
return;
}
FormGroupLocked(1);
diff --git a/src/components/screens/MatchmakingScreen.tsx b/src/components/screens/MatchmakingScreen.tsx
index 44db9cf..29752f9 100644
--- a/src/components/screens/MatchmakingScreen.tsx
+++ b/src/components/screens/MatchmakingScreen.tsx
@@ -140,7 +140,7 @@ export function MatchmakingScreen() {
{searching && (
<>
{elapsed}s
- {elapsed >= 90 ? (
+ {elapsed >= 40 ? (
{t("mm.stuck")}
) : (
{t("mm.fillHint")}