serverCore

All-servers-vote server-core placement: core holds every voter seat of every game.

A peer whose id is in core seats itself as a voter (voters = core); any other peer starts as a self-declared learner (voters = core, learners = {self}) and is admitted into the replicated config by the core leader's learner-admission loop (launched by gameNode on core members). Players propose via learner→leader forwarding and receive the committed log — the same TurnSequencer/GameSession code as SessionOwned.

Bootstrap via gameNode with placement = serverCore(core) on every peer, core and player alike — or, for many concurrent games over one connection set, via gameNodeRoom per game (the game-per-room composition: each game's room seam scopes the admission loop to exactly that game's players). The appoint-the-host paths (gameHost/gameJoin/gameSpectate) reject this seating — their admission machinery promotes session peers to voters, which contradicts a fixed core.

Parameters

core

The NodeIds of the server core — every one of them votes in this game. Must be non-empty.

Samples

runTest(StandardTestDispatcher(), timeout = TEST_WEDGE_BACKSTOP) {
    val loom = InMemoryLoom()
    val server1 = loom.host(Pattern("hosted-game"))
    val server2 = loom.join(InMemoryTag("server-2"))
    val server3 = loom.join(InMemoryTag("server-3"))
    val playerSeam = loom.join(InMemoryTag("player"))

    val core = setOf(
        NodeId(server1.selfId.value),
        NodeId(server2.selfId.value),
        NodeId(server3.selfId.value),
    )
    val placement = ConsensusPlacement.serverCore(core)

    // Seeded per-node RNG breaks election-timeout symmetry deterministically under virtual time.
    fun config(seed: Long) = RaftConfig(expectVirtualTime = true, random = Random(seed))

    backgroundScope.gameNode(server1, core, raftConfig = config(1), placement = placement)
    backgroundScope.gameNode(server2, core, raftConfig = config(2), placement = placement)
    backgroundScope.gameNode(server3, core, raftConfig = config(3), placement = placement)
    val player = backgroundScope.gameNode(playerSeam, core, raftConfig = config(4), placement = placement)

    // The core elects among the servers; the player is admitted as a learner and proposes via
    // learner→leader forwarding — the same TurnSequencer code as every other placement.
    val game = TurnSequencer(player.node, Int.serializer())
    val move = game.propose(1)
    assertEquals(1, move.action)
}