gameJoinRoom

suspend fun CoroutineScope.gameJoinRoom(rooms: Loom, gameId: String, storage: RaftStorage = InMemoryRaftStorage(), raftConfig: RaftConfig = RaftConfig(), joinAdmissionTimeout: Duration = DEFAULT_JOIN_ADMISSION_TIMEOUT, random: Random = Random.Default, clock: () -> Instant, identity: ClientIdentity = ClientIdentity.Auto, placement: ConsensusPlacement = ConsensusPlacement.SessionOwned): GameSession

Join the hosted game in the room named gameId over rooms — the game-per-room analogue of gameJoin.

rooms is this player's session-mux Loom (a us.tractat.kuilt.core.MuxClientLoom over one base connection). Joining several games through the same loom multiplexes them all over that single connection — the channel name is the (gameId) envelope the server routes on. The room channel is wrapped in the star-relay layer (starOverlay) so this player speaks the same relay envelope as the hosting side, then gameJoin runs on it unchanged.

Parameters are forwarded to gameJoin unchanged; see gameJoin for their semantics.

Parameters

placement

Must seat AuthoritySeating.SessionPeers (see gameJoin). To ride a server-core game as a learner use gameNodeRoom with ConsensusPlacement.serverCore.

Samples

runTest(StandardTestDispatcher(), timeout = TEST_WEDGE_BACKSTOP) {
    val dispatcher = requireNotNull(coroutineContext[kotlin.coroutines.ContinuationInterceptor])
    val fabric = InMemoryRoomFabric(backgroundScope, dispatcher, random = Random(0))
    val clock = { Instant.fromEpochMilliseconds(0) }
    fun config(seed: Long) = RaftConfig(expectVirtualTime = true, random = Random(seed))

    // Room "casual": appoint-the-host — the server hosts, alice takes a voter seat.
    val casualHost = async {
        backgroundScope.gameHostedRoom(
            fabric.serverLoom, "casual", peerCount = 2,
            raftConfig = config(1), random = Random(11), clock = clock,
        )
    }
    val casualAlice = async {
        backgroundScope.gameJoinRoom(
            fabric.clientLoom(PeerId("alice"), Random(21)), "casual",
            raftConfig = config(2), random = Random(12), clock = clock,
        )
    }

    // Room "ranked": server-core — every voter seat is the server's; bob rides as a learner.
    val core = setOf(NodeId("server"))
    val placement = ConsensusPlacement.serverCore(core)
    backgroundScope.gameNodeRoom(
        fabric.serverLoom, "ranked", voterIds = core,
        raftConfig = config(3), random = Random(13), clock = clock, placement = placement,
    )
    val rankedBob = backgroundScope.gameNodeRoom(
        fabric.clientLoom(PeerId("bob"), Random(22)), "ranked", voterIds = core,
        raftConfig = config(4), random = Random(14), clock = clock, placement = placement,
    )

    // Both games converge independently — same TurnSequencer code, different authority.
    val casualMove = TurnSequencer(casualAlice.await().node, Int.serializer()).propose(1)
    val rankedMove = TurnSequencer(rankedBob.node, Int.serializer()).propose(2)
    assertEquals(1, casualMove.action)
    assertEquals(2, rankedMove.action)
    casualHost.await().close()
}