gameHostedRoom

suspend fun CoroutineScope.gameHostedRoom(rooms: Loom, gameId: String, peerCount: Int, returnAt: ReturnPolicy = ReturnPolicy.FullMembership, storage: RaftStorage = InMemoryRaftStorage(), raftConfig: RaftConfig = RaftConfig(), livenessConfig: HeartbeatConfig? = null, random: Random = Random.Default, clock: () -> Instant, identity: ClientIdentity = ClientIdentity.Auto, placement: ConsensusPlacement = ConsensusPlacement.SessionOwned): GameSession

Host one appoint-the-host game in the room named gameId over rooms — the game-per-room analogue of gameHosted.

rooms is the serving side's session-mux Loom (a us.tractat.kuilt.core.MuxServerLoom over the server's accept source). rooms.host(Pattern(gameId)) yields that room's us.tractat.kuilt.core.RoomHubSeam; this function wraps it in the star-relay layer (starOverlay) and runs gameHost on it. Call it once per concurrent game — every game gets its own room, its own Raft cluster, its own presence channel, and its own app-channel namespace, all sharing the one connection set. Players join with gameJoinRoom using the same gameId.

Parameters are forwarded to gameHost unchanged; see gameHost for their semantics. random and clock additionally drive the room's relay overlay (seeded/virtual-time injectable, like gameHosted).

Parameters

placement

How this session obtains its consensus node — must seat AuthoritySeating.SessionPeers (see gameHost). For the server-core placement 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()
}