The whole mandatory contract is two messages you send and five you react to. Playlists, voting, controllers, transitions β that's the daemon's problem, not yours.
func _ready() -> void:
GameNight.prepared.connect(_on_prepared)
GameNight.started.connect(_on_started)
GameNight.disposed.connect(_on_disposed)
func _on_prepared(session, seats, players) -> void:
load_arena(seats) # one pawn per occupied seat
GameNight.notify_ready(session) # show nothing yet!
func _on_started(_session) -> void:
start_match() # gameplay within a second
func _match_over() -> void:
GameNight.notify_finished(session)
# keep rendering the scoreboard until `disposed`
Same shape in Rust, or in 60 lines of dependency-free JavaScript β it's a WebSocket and seven JSON messages. The protocol is the product; SDKs are convenience.
$ gamenight-certify my-game -- ./my-game
βββββββββββββββββββββββββββββββββββββ
GameNight conformance Β· my-game
βββββββββββββββββββββββββββββββββββββ
β process connects and says hello (487 ms)
β prepare β ready β instant start (warm in 42 ms)
β match ends with a single `finished`
β replay vote β fresh session, same process
β pause and resume tolerated mid-match
β mid-match skips: dispose, re-prepare, stay resident
β process resident for the whole night
βββββββββββββββββββββββββββββββββββββ
7/7 passed β my-game is party-ready π
A scripted night runs against your real build β launch handshake, warm-up, replay, five mid-match skips. Exit 0 = party-ready, so it drops straight into your CI.
Everything below is rendered live from docs/integrating-your-game.md β the same file that ships in the repo. One source of truth.
Loading the guideβ¦