diff --git a/Game/scripts/server_boot.gd b/Game/scripts/server_boot.gd index 36881e20..af9aa66f 100644 --- a/Game/scripts/server_boot.gd +++ b/Game/scripts/server_boot.gd @@ -51,6 +51,15 @@ func _ready() -> void: ServerLog.configure(String(config.get_value("log-level"))) var port := int(config.get_value("port")) var max_clients := int(config.get_value("max-clients")) + var allocated_mode := bool(config.get_value("allocated-mode")) + var assigned_transport := String(config.get_value("transport")) + # Hosted SDR is not wired into the Godot transport layer yet. Refuse the + # allocated launch rather than silently opening an ENet endpoint that does + # not match the signed assignment's transport contract. + if allocated_mode and assigned_transport != NetworkManager.TRANSPORT_ENET: + printerr("cosmic-clash-server: allocated transport '%s' is not supported by this build" % assigned_transport) + get_tree().quit(1) + return NetworkManager.client_connected.connect(_on_client_connected) NetworkManager.client_disconnected.connect(_on_client_disconnected) @@ -66,8 +75,13 @@ func _ready() -> void: ServerLog.info("server_started", { "port": port, "max_clients": max_clients, "log_level": ServerLog.level_name(), "min_players": int(config.get_value("min-players")), - "max_matches": int(config.get_value("max-matches")), + "max_matches": 1 if allocated_mode else int(config.get_value("max-matches")), "arena_rotation": String(config.get_value("arena-rotation")), + "allocated_mode": allocated_mode, + "match_id": String(config.get_value("match-id")) if allocated_mode else "", + "server_id": String(config.get_value("server-id")) if allocated_mode else "", + "region": String(config.get_value("region")) if allocated_mode else "", + "transport": assigned_transport if allocated_mode else NetworkManager.TRANSPORT_ENET, }) _last_physics_frame = Engine.get_physics_frames() @@ -81,7 +95,7 @@ func _install_match_loop() -> void: loop.name = "ServerMatchLoop" loop.min_players = int(config.get_value("min-players")) loop.start_countdown_seconds = float(config.get_value("start-countdown")) - loop.max_matches = int(config.get_value("max-matches")) + loop.max_matches = 1 if bool(config.get_value("allocated-mode")) else int(config.get_value("max-matches")) loop.rotation_mode = String(config.get_value("arena-rotation")) get_tree().root.add_child.call_deferred(loop) @@ -118,4 +132,3 @@ func _on_player_joined(peer_id: int, player_name: String) -> void: func _on_player_left(peer_id: int) -> void: ServerLog.info("player_left", {"peer_id": peer_id, "roster": MatchNet.roster.size()}) - diff --git a/multiplayer-todo.md b/multiplayer-todo.md index 2f891ba3..1e0a6afe 100644 --- a/multiplayer-todo.md +++ b/multiplayer-todo.md @@ -1173,7 +1173,7 @@ the local/CI/community transport, not a silent production fallback. | 8.3 `[D:8.1]` | **DONE.** Publish versioned OpenAPI + WebSocket contracts for Steam login/session, profile/rating, queue create/heartbeat/cancel/resume, proposal accept/decline, assignment/status, server registration/roster/result/shutdown | [`server/contracts/v1/`](server/contracts/v1/) contains machine-readable REST/events contracts and dependency-free structural tests; REST resync is specified by the contract; `server/api/service.go` also exposes the documented `/api/v1` route names (including server-assigned idempotent queue ticket IDs and DELETE cancellation) alongside the existing client `/v1` routes, covered by `TestDocumentedContractRoutesAdaptToServiceAPI` | | 8.4 `[D:8.3]` | **DONE.** Define opaque IDs, legal queue/match state transitions, revisions and idempotency keys | [`server/contracts/v1/state-transitions.json`](server/contracts/v1/state-transitions.json) locks terminal states, legal edges, stale-revision handling and same-key replay/conflict behavior; contract tests cover the invariants | | 8.5 `[D:8.4]` | **IN PROGRESS.** Initial PostgreSQL migration now defines durable idempotency keys, queue ownership/active-participation fencing, identities, sessions/revocations, ranked seasons, ratings/events, matches/participants, penalties, results, audits and outbox | `server/migrations/0001_initial.sql` and static checks cover the durable tables, uniqueness/check constraints and Redis-as-cache boundary; live PostgreSQL up/rollback/forward migration, serializable adapters and cache-loss repair remain | -| 8.6 `[D:8.3,8.4]` | **IN PROGRESS.** Add allocated-mode `ServerConfig` compatibility fields as opt-in defaults | `ServerConfig` now validates allocation mode, match/server IDs, playlist version, client build, future assignment expiry, image digest, transport and EU/NA region; signed-authorisation admission and full manifest/runtime tests remain | +| 8.6 `[D:8.3,8.4]` | **IN PROGRESS.** Add allocated-mode `ServerConfig` compatibility fields as opt-in defaults | `ServerConfig` now validates allocation mode, match/server IDs, playlist version, client build, future assignment expiry, image digest, transport and EU/NA region; `server_boot.gd` fails closed for the not-yet-wired Steam SDR transport, constrains allocated processes to one match, and emits allocation identity/transport in `server_started`; signed-authorisation admission, dynamic endpoint wiring and full manifest/runtime tests remain | #### 8B — Authentication and secure control plane