import type { GameServerStatus, SessionVisibility } from "./hostPersistence.js"; /** * Room-topology ceiling on `slotsPerServer`; shared membership uses indexed rows instead. * * A hosted server keeps its whole roster, every member's session state, and the world row in one * backing document. On Convex that document caps at 1 MiB with at most 1024 entries in an object, and * the host rewrites the session map on every persist — so member count is a per-transaction cost, not * just a number in a config. 256 leaves room for a real world row and a real session blob per member * inside those limits. A world that needs more concurrent players needs more than one server row. */ export declare const JG_MAX_MEMBERS_PER_SERVER = 256; /** Whether a `slotsPerServer` value is a capacity a single hosted server row can actually hold. */ export declare function validateSlotsPerServer(slotsPerServer: number, topology?: "rooms" | "shared"): { ok: true; } | { ok: false; reason: string; }; /** * How auto-match behaves when no `serverId` is supplied. * * - `"auto"` — fill open public rooms, create a new one when none has capacity (many parallel rooms). * - `"singleton"` — one shared world per game: always match the existing room, and refuse the join * when it is full rather than silently starting a second world the player would be alone in. */ export type MatchmakingMode = "auto" | "singleton"; /** A row `selectJoinTarget` can choose between — the matchmaking-relevant fields of a server record. */ export type JoinCandidate = { memberUserIds: readonly string[]; slotsPerServer: number; visibility?: SessionVisibility | undefined; createdAt: number; }; /** What a join should do with the candidate rows it found. */ export type JoinTarget = { kind: "join"; row: T; } | { kind: "create"; } | { kind: "refuse"; reason: string; }; /** * Resolve an auto-match join against the game's joinable rows under a {@link MatchmakingMode}. * Reach for it instead of hand-rolling the "find a room with space, else make one" scan, which is * where a singleton world silently shards into per-player copies. * @capability host-join-target choose or refuse an auto-matched server under a matchmaking mode */ export declare function selectJoinTarget(rows: readonly T[], args: { userId: string; mode?: MatchmakingMode; /** Capacity to judge rows against — the host's current option, when it supersedes the stored value. */ slotsPerServer?: number; }): JoinTarget; /** * True when a server's `visibility` should surface in public listings / browse results. * @capability host-listing-filter exclude private sessions from public browse results */ export declare function isListablePublicly(visibility: SessionVisibility | undefined): boolean; /** * Private-server join-code gate. Existing members always pass; non-members must present a * matching `joinCode` (loose-normalized via {@link normalizeJoinCode}). Callers still decide * whether the server is private — this only answers the code/membership half. * @capability host-join-code-gate membership-or-code gate for private hosted servers */ export declare function canJoinPrivateServer(args: { isMember: boolean; joinCode: string | undefined; suppliedCode: string | undefined; }): boolean; /** Whether `userId` is already on the server's member roster. */ export declare function isServerMember(memberUserIds: readonly string[], userId: string): boolean; /** * True when the server has no free slots for a non-member. Existing members never count as * "full" so rejoin/leave cycles keep working. */ export declare function isServerFull(memberCount: number, slotsPerServer: number, isMember: boolean): boolean; export declare function isServerFull(memberUserIds: readonly string[], slotsPerServer: number, userId: string): boolean; /** * Whether a private-visibility server blocks this join (non-member without a matching code). * Public / undefined visibility never blocks. */ export declare function isPrivateJoinBlocked(args: { visibility: SessionVisibility | undefined; memberUserIds: readonly string[]; userId: string; joinCode: string | undefined; suppliedCode: string | undefined; }): boolean; /** Roster after a successful join: unchanged when already a member, else appended. */ export declare function withJoinedMember(memberUserIds: readonly string[], userId: string): string[]; /** Roster after a leave: `userId` removed; order of remaining members preserved. */ export declare function withoutMember(memberUserIds: readonly string[], userId: string): string[]; /** Status after a leave: empty rooms reopen; non-empty rooms keep their current status. */ export declare function statusAfterLeave(remainingMemberCount: number, currentStatus: GameServerStatus): GameServerStatus; /** * Auto-match candidate when no `serverId` is supplied: already a member, or a public room with * free capacity. Private rooms are never auto-picked (join-by-code / direct id only). */ export declare function isAutoJoinCandidate(args: { memberUserIds: readonly string[]; slotsPerServer: number; visibility: SessionVisibility | undefined; userId: string; }): boolean;