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
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)
}