feat(multiplayer): rotate ranked arenas deterministically

This commit is contained in:
Josh Creek
2026-09-01 21:06:26 +01:00
parent 84372204fd
commit ff80ab46b7
5 changed files with 45 additions and 5 deletions
+1 -1
View File
@@ -107,7 +107,7 @@ func main() {
if err != nil {
return domain.PreparedProposal{}, err
}
return domain.PrepareProposal(id, playlist, formation, participants, domain.DefaultRankedArena(), at)
return domain.PrepareProposal(id, playlist, formation, participants, domain.RankedArenaForProposal(id), at)
}
return domain.PrepareProposal(id, playlist, formation, nil, domain.RankedArena{}, at)
},
+18 -1
View File
@@ -1,6 +1,9 @@
package domain
import "fmt"
import (
"crypto/sha256"
"fmt"
)
type RankedParticipant struct {
PlayerID string
@@ -25,6 +28,8 @@ var rankedArenas = map[string]RankedArena{
"arena_03": {ID: "arena_03", Path: "res://scenes/arena_03.tscn"},
}
var rankedArenaOrder = []string{"arena_01", "arena_02", "arena_03"}
// DefaultRankedArena supplies a safe server-owned eligibility decision while
// the allocator-to-Godot match configuration channel is being completed. A
// ranked proposal is never admitted based on a mutable command-line boolean.
@@ -32,6 +37,18 @@ func DefaultRankedArena() RankedArena {
return rankedArenas["arena_01"]
}
// RankedArenaForProposal chooses a floor-goal arena deterministically from the
// proposal identity. The same durable proposal retry therefore cannot change
// arena, while independent proposals rotate across the registry without
// mutable worker-local counters.
func RankedArenaForProposal(proposalID string) RankedArena {
if proposalID == "" {
return DefaultRankedArena()
}
digest := sha256.Sum256([]byte(proposalID))
return rankedArenas[rankedArenaOrder[int(digest[0])%len(rankedArenaOrder)]]
}
func ValidateRankedAdmission(participants []RankedParticipant, arena RankedArena) error {
if len(participants) != 6 || !validRankedArena(arena) {
return fmt.Errorf("ranked admission requirements not met")
+22 -1
View File
@@ -1,6 +1,9 @@
package domain
import "testing"
import (
"fmt"
"testing"
)
func rankedParticipants() []RankedParticipant {
result := make([]RankedParticipant, 6)
@@ -44,3 +47,21 @@ func TestRankedArenaRegistryExcludesElevatedVariants(t *testing.T) {
}
}
}
func TestRankedArenaForProposalIsStableAndRotatesEligibleRegistry(t *testing.T) {
first := RankedArenaForProposal("proposal-stable")
if first != RankedArenaForProposal("proposal-stable") {
t.Fatal("same proposal selected different arenas")
}
seen := map[string]bool{}
for index := 0; index < 128; index++ {
arena := RankedArenaForProposal(fmt.Sprintf("proposal-%d", index))
if !validRankedArena(arena) {
t.Fatalf("proposal selected ineligible arena %+v", arena)
}
seen[arena.ID] = true
}
if len(seen) != len(rankedArenaOrder) {
t.Fatalf("selected arenas = %v, want every eligible arena", seen)
}
}