Docs / Learn / Where next

Where next

leave the guided track knowing you have the whole loop, and knowing exactly which reference guide to open for each thing you did not build here.

You have built the movement core of the Arena Shooter sample: a top-down arena where the server moves your fighter and every client sees it move. Play the full sample - movement plus shooting, bots, boons, modifier voting, and a kills leaderboard - on the samples page.

The full sample layers four features on top of exactly the loop you wrote, each on its own public reference door: matchmaking fills empty arena slots and groups fighters into rounds, bots take the slots no human filled, voting runs the between-rounds modifier pick, and leaderboards tracks kills across the arena. Shooting and boons are ordinary bundle logic on the same server-authoritative loop - client intent in, server-decided state out.

You built a backend, one concept at a time. You pushed a Lua bundle, connected a client and proved it talked, resolved a guest identity that survives a restart, stored and re-read a value, ran an arena round (join, input, server-moved fighter, broadcast state, finish), and ran a persistent arena (join, tick deltas, finish). That is the complete server-authoritative loop: the client sends intent, the server decides, the server broadcasts state. Nothing below changes that rule.

The track stops here on purpose. The rest of Asobi is not a longer tutorial - it is a set of features you switch on when you need them, each documented in its own reference guide. You already know how to read them: every one is the same "client sends a frame, server decides, server pushes state" shape you have been writing all along.

The extensions, one pointer each

Everything here is off the linear path so that onboarding was never "all at once".

  • Chat - namespaced channels (room:, world:, zone:, prox:, dm:), join and send frames, chat.message push. No bundle code required; the runtime owns it. See the Chat section of the websocket-protocol reference; message history is REST (rest-api).
  • Voting - in-match group decisions (path picks, run modifiers) via the vote_requested / vote_resolved callbacks and vote.cast. See voting.
  • Matchmaking - the periodic-tick matchmaker groups tickets into matches by a per-mode strategy; client sends matchmaker.add, server pushes match.matched. See matchmaking.
  • In-app purchases - server-side receipt validation for Apple App Store and Google Play (POST /api/v1/iap/apple, /api/v1/iap/google). See iap.
  • Presence - presence.update intent, presence.changed push; in a world it is free, since the tick loop already broadcasts who is where. See the Presence section of websocket-protocol and lobbies.
  • Notifications - notification.new push plus the REST list/read/delete endpoints. See the Notifications section of websocket-protocol and rest-api.

The two full references

When a one-liner above is not enough, these are the complete doors:

  • WebSocket protocol reference - every realtime frame the server accepts and every push it sends, across session.*, match.*, world.*, chat.*, presence.*, notification.*, and voting.
  • SDK reference - the per-engine client guide for whichever SDK you installed in step 2 (Defold, Godot, Unity, Unreal, Dart, JS, or LOVE), plus the rest-api reference for the request/response surface. The only thing that differs between engines is syntax; the frames on the wire are identical.

Cloud and self-hosted: what still differs

The game logic and every client SDK call are identical on cloud and self-hosted; the only routine difference is the base server URL, which you set once. Write it once, run it anywhere.

The exception is any feature that needs an operator secret:

  • Cloud (asobi deploy / console.asobi.dev) auto-provisions per-environment secrets - the guest pepper and the per-project database - so a bundle that is off in dev is on in prod with no change.
  • Self-hosted (your own release of asobi + asobi_lua, your own Postgres) you supply those yourself: the guest pepper via ASOBI_GUEST_VERIFIER_PEPPER, and, for IAP, the store credentials via sys.config (apple_bundle_id, the Google service-account key). The feature code is the same; only where the secret comes from changes.

Checkpoint

Prove the reference door works against the backend you already built - no bundle change, no new SDK code. With asobi dev (or your self-hosted release) running, open two raw WebSocket connections to /ws and authenticate each with session.connect. Note the two player_ids the server returns; call them <A> and <B>. On both connections join the direct-message channel for that pair (the server authorises each end because each is one of the two named players):

{"type": "chat.join", "payload": {"channel_id": "dm:<A>:<B>"}}

Then from one connection send:

{"type": "chat.send", "payload": {"channel_id": "dm:<A>:<B>", "content": "loop complete"}}

You should see a chat.message push arrive on the other connection carrying "content": "loop complete" and a sender_id. A third connection that tries to join the same dm: channel is refused with not_authorized - the server decides who may read, exactly as it decides everything else. That is a feature you never wrote Lua for, reached the same way as everything you did write: client intent in, server-decided state out. You now have the whole loop and a map of the rest.

Next

The track is done. The reference door - every guide named above - is yours; open the one your next feature needs.