fix(multiplayer): gate allocated matches on full roster

This commit is contained in:
Josh Creek
2026-09-01 16:03:43 +01:00
parent 498f219ab9
commit bdb3c8f4a7
3 changed files with 19 additions and 0 deletions
+10
View File
@@ -83,6 +83,10 @@ func _ready() -> void:
get_tree().quit(1)
return
_control = ServerControlScript.new()
# An allocated process owns exactly the roster issued for this match.
# Never let the general-purpose direct-server default (one player) start
# an allocated match with only a partial assignment admitted.
config.values["min-players"] = required_min_players(true, roster_tokens.size(), int(config.get_value("min-players")))
_control.name = "ServerControl"
_control.drain_requested.connect(_on_drain_requested)
get_tree().root.add_child.call_deferred(_control)
@@ -180,3 +184,9 @@ func _on_drain_requested() -> void:
_drain_requested = true
MatchNet.admissions_open = false
ServerLog.info("server_draining", {"reason": "control_request"})
static func required_min_players(allocated: bool, roster_size: int, configured: int) -> int:
if allocated and roster_size > 0:
return roster_size
return configured
+7
View File
@@ -157,3 +157,10 @@ func test_allocated_mode_rejects_missing_or_expired_assignment_manifest_fields()
assert_true(not missing.is_valid(), "client build and expiry are required")
var expired = _parse(["--allocated-mode", "--match-id=m", "--server-id=s", "--playlist-version=v", "--client-build=client", "--assignment-expiry-unix=1", "--server-image-digest=sha256:" + "a".repeat(64), "--transport=enet", "--region=EU"])
assert_true(not expired.is_valid(), "expired assignment is rejected")
func test_allocated_start_floor_is_the_verified_roster_size() -> void:
var boot = preload("res://scripts/server_boot.gd")
assert_eq(boot.required_min_players(true, 6, 1), 6, "allocated six-player roster cannot start with one player")
assert_eq(boot.required_min_players(true, 2, 6), 2, "allocated casual roster uses its complete size")
assert_eq(boot.required_min_players(false, 1, 1), 1, "direct server keeps its configured floor")
+2
View File
@@ -1396,3 +1396,5 @@ Bounded, but not by much: the attacker must race a genuine disconnect, and they
The current working implementation now wires `deploy/k8s/base/fleet.yaml` to the digest-pinned `game-server` supervisor target, the in-cluster control-plane Service, workload roster materialization, signing/drain secret references, downward-API server/image identity, and the required game-server egress policy. `kubectl kustomize deploy/k8s/base` and `server/security/test_fleet_manifests.py` pass. The older 8.28 narrative above still records the pre-wiring state; live Agones, operator secret/image replacement, and real cluster readiness remain explicit gates.
The NA overlay now also patches the allocated childs `--region=NA` argument, keeping it aligned with the NA Fleet label; rendered EU and NA overlays and the adversarial manifest test verify that regional assignment validation cannot silently remain EU in the NA deployment.
Allocated Godot startup now derives its `min-players` floor from the verified signed roster size, preventing the direct-server default of one player from starting a partially admitted allocated match. A focused regression test covers six-player, casual two-player, and direct-server behavior; the full Godot harness is currently unavailable because Godot cannot open its shared `user://` log and crashes in the macOS renderer before test execution.