diff --git a/multiplayer-next.md b/multiplayer-next.md index c89349c4..5964f590 100644 --- a/multiplayer-next.md +++ b/multiplayer-next.md @@ -1249,7 +1249,7 @@ production fallback. |---|---|---| | 8.44 `[D:8.3,8.4,8.28,8.31]` | **LOCAL COMPLETE; PRODUCTION GATE OPEN.** Go observability package encodes queue/proposal/match/server IDs and lifecycle stage while recursively redacting auth/relay tokens and credentials. `Service.Log` is wired to mutation and read routes at every outcome, and `cmd/control-plane` writes those events as JSON lines to stderr | `server/observability/` covers correlation fields, nested secret redaction, content-aware credential canaries and unnamed-event rejection; API tests cover lifecycle event wiring without logging error text. A production metrics/traces backend and dashboard/alert routing remain open; the local logger is intentionally stderr-only | | 8.45 `[D:8.2,8.44]` | **LOCAL COMPLETE; PRODUCTION GATE OPEN.** Go observability package turns the documented RTT, allocation/connect latency, result-success, API-latency and tick/headroom thresholds into executable window checks; the API exporter emits a bounded cumulative latency histogram suitable for querying the documented p95 API SLO | `server/observability/slo.go`, `metrics.go`, `deploy/observability/prometheus-rules.yaml` and adversarial tests cover healthy/violating/empty windows, fixed operation/status labels, cumulative bucket boundaries, arbitrary-path cardinality safety, and optional API p95/5xx alerts. Production scrape configuration, alert routing, wait/MMR/proposal/flood/cost series and runbooks remain | -| 8.46 `[D:8.5,8.7,8.9,8.10,8.14,8.18,8.21,8.23,8.25]` | **IN PROGRESS.** Go unit/race coverage spans the current domain/store/supervisor policies, and fuzz targets now exercise queue input, result payload hashing and revision events | `server/domain/*_test.go`, `server/store/*_test.go`, `server/supervisor/*_test.go`, `server/migrations/*_test.go` and `server/domain/fuzz_test.go` pass normal/race suites; `go test -race ./...` passes across API, domain, migrations, observability, store, supervisor and testkit; `go vet ./...` passes; each of the three declared domain fuzz targets passes a bounded 4-second run; PostgreSQL live migration execution now runs clean (§8.5), and four real-concurrency cases are covered against a live database with `-race`: §8.14's queue-heartbeat revision race, §8.18's two-matcher contested-ticket race, §8.30's cross-allocator-replica capacity race, and §8.21/§8.25's concurrent identical-result-submission race; the "lost Redis" fixture is covered live against a real server (§8.14: real TTL expiry, repair-after-`FLUSHALL`; fake Steam/allocator fixtures are §8.47's testkit, already done). Further transaction fixtures (e.g. concurrent proposal-recovery expiry races, live Redis failover mid-write under load) remain | +| 8.46 `[D:8.5,8.7,8.9,8.10,8.14,8.18,8.21,8.23,8.25]` | **IN PROGRESS.** Go unit/race coverage spans the current domain/store/supervisor policies, and fuzz targets now exercise queue input, result payload hashing and revision events. The local gate falls back to the pinned headless Godot container when a native executable is unavailable, so its full cross-language suite remains runnable without an image export | `scripts/verify_multiplayer_local.sh` passed end to end on the current tree: Go normal/race/vet, all three bounded fuzz targets, 207 Godot tests, contracts, migrations, and manifests. `server/domain/*_test.go`, `server/store/*_test.go`, `server/supervisor/*_test.go`, `server/migrations/*_test.go` and `server/domain/fuzz_test.go` provide the underlying coverage; PostgreSQL live migration execution now runs clean (§8.5), and four real-concurrency cases are covered against a live database with `-race`: §8.14's queue-heartbeat revision race, §8.18's two-matcher contested-ticket race, §8.30's cross-allocator-replica capacity race, and §8.21/§8.25's concurrent identical-result-submission race; the "lost Redis" fixture is covered live against a real server (§8.14: real TTL expiry, repair-after-`FLUSHALL`; fake Steam/allocator fixtures are §8.47's testkit, already done). Further transaction fixtures (e.g. concurrent proposal-recovery expiry races, live Redis failover mid-write under load) remain | | 8.47 `[D:8.7,8.30]` | **IN PROGRESS.** Offline testkit provides deterministic fake Steam verification and fake allocation with forced failure injection; the independent Compose runner drives fake-Steam session issuance, real HTTP queue create/heartbeat/cancel, matcher-backed six-player proposal formation/acceptance, and idempotency-conflict checks | `server/testkit/` covers verified identity/replay, unknown identity, wrong App ID, expiry, no capacity, compatibility-key conflict, idempotent allocation replay and cloud-free forced allocation failure; the Compose API/matcher slice is wired into CI, while live exhaustive matrix and production Steam remain | | 8.48 `[D:8.10,8.14,8.17,8.18,8.27,8.31,8.35,8.47]` | **IN PROGRESS.** Offline testkit exercises verified queue projection → ranked six-player proposal → ENet allocation → assignment-ready manifest → certified durable result receipt; `compose.allocated-smoke.yml` independently runs the real testkit API, matcher, allocator, maintenance, Agones-shaped provider, PostgreSQL, and game-server supervisor with a generated signed roster, verifying an expired ranked reconnect becomes one durable abandonment/cooldown, queue/proposal/allocation binding, authenticated result, idempotent retry, shutdown acknowledgment, durable receipt/audit rows, and SIGTERM-driven game-process drain | `.github/workflows/allocated-compose.yml` runs `make verify-allocated-compose`. Live Docker evidence from this workspace and legacy fixture non-regression remain open | | 8.49 `[D:8.25,8.26,8.28,8.29,8.30,8.31,8.35,8.36]` | **IN PROGRESS.** `scripts/verify_kind_agones.sh` creates a disposable kind cluster, installs pinned Agones, loads the real `game-server` image, applies the Fleet in an explicitly separate Agones-only supervisor/UDP readiness mode, and verifies readiness plus allocation of a dynamic UDP endpoint; `.github/workflows/agones-integration.yml` runs it for infrastructure changes and on demand | The cloud-free runner is committed and fails clearly when Docker/kind/Helm are unavailable. CI/live evidence for production control-plane registration, roster/no-show, both readiness stages, races, multi-match node, result-pending reconciliation, drain, and rollback remains open | diff --git a/scripts/test_verify_multiplayer_local.py b/scripts/test_verify_multiplayer_local.py new file mode 100644 index 00000000..25279514 --- /dev/null +++ b/scripts/test_verify_multiplayer_local.py @@ -0,0 +1,19 @@ +from pathlib import Path +import unittest + + +ROOT = Path(__file__).parents[1] + + +class LocalMultiplayerGateTest(unittest.TestCase): + def test_godot_gate_has_a_digest_pinned_container_fallback(self): + script = (ROOT / "scripts" / "verify_multiplayer_local.sh").read_text() + self.assertIn("run_godot_harness()", script) + self.assertIn("barichello/godot-ci@sha256:", script) + self.assertIn("docker run --rm --platform linux/amd64", script) + self.assertIn("type=bind,src=$root_dir,dst=/workspace", script) + self.assertIn("Godot executable not found", script) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/verify_multiplayer_local.sh b/scripts/verify_multiplayer_local.sh index 5c172362..0b44d8fd 100755 --- a/scripts/verify_multiplayer_local.sh +++ b/scripts/verify_multiplayer_local.sh @@ -3,11 +3,23 @@ set -euo pipefail root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" godot_bin="${GODOT_BIN:-/Applications/Godot.app/Contents/MacOS/Godot}" +godot_image="barichello/godot-ci@sha256:622e5ca81b54cd8038ecf7de5d157b47efc800d7cf635af2eec18a6aee4bab7e" -if [[ ! -x "$godot_bin" ]]; then - echo "local multiplayer gate: Godot executable not found: $godot_bin" >&2 - exit 2 -fi +run_godot_harness() { + if [[ -x "$godot_bin" ]]; then + "$godot_bin" --headless --path "$root_dir/Game" res://tests/test_runner.tscn + return + fi + if ! command -v docker >/dev/null 2>&1 || ! docker info >/dev/null 2>&1; then + echo "local multiplayer gate: Godot executable not found ($godot_bin), and Docker is unavailable for the pinned headless fallback" >&2 + return 2 + fi + echo "local multiplayer gate: using pinned headless Godot container fallback" + docker run --rm --platform linux/amd64 \ + --mount "type=bind,src=$root_dir,dst=/workspace" \ + -w /workspace "$godot_image" \ + godot --headless --path Game res://tests/test_runner.tscn +} echo "local multiplayer gate: Go tests" (cd "$root_dir/server" && go test ./...) @@ -22,7 +34,7 @@ echo "local multiplayer gate: bounded fuzz targets" (cd "$root_dir/server" && go test ./domain -fuzz FuzzSyncEventApplicationDoesNotPanic -fuzztime=2s) echo "local multiplayer gate: Godot harness" -"$godot_bin" --headless --path "$root_dir/Game" res://tests/test_runner.tscn +run_godot_harness echo "local multiplayer gate: contracts and manifests" python3 -m json.tool "$root_dir/server/contracts/v1/openapi.json" >/dev/null