Spatial

Travel & Respawn

Bind/recall points, a warp destination table, and death→respawn location resolution through an ORDERED strategy chain (bind → nearest zone point → your own IRespawnStrategy). WHEN travel is allowed is the game's policy SPIs (the default DENIES client respawns until one is composed — the Stats-depletion bridge supplies the classic 'dead may go home'); moves ride the ITravelMover seam (Movement's prediction-aware Teleport when composed).

Server
services.AddCrossplayTravel();
Unity package
com.crossplay.travel
Depends on
world

Seams you implement 1

Crossplay calls these; your game supplies them. Each ships an inert or permissive default, so register yours before services.AddCrossplayTravel(); and it wins.

IRespawnStrategy game SPI

One link of the respawn WHERE chain. Strategies are registered in DI (built-ins: "bind", "zone"; add your own) and ORDERED by RespawnChain — the first that resolves wins, exactly the donor-proven fallback shape (bind point → graveyard → town) with the vocabulary left to the game.

  • string Name { get; }

    The name RespawnChain selects this strategy by.

  • bool TryResolve(long ownerId, WorldEntity entity, out TravelLocation location)

    True when this strategy can place the entity, yielding the location.

Providers you can swap 5

Infrastructure seams. Crossplay ships a working implementation of each — replacing one changes where data lives or how it moves, never a game rule.

IBindPointStore provider

Storage seam for per-player bind points. The default is in-memory (binds reset with the server process); the Hosting composition bridges PlayerData for durability when that piece is present, and a game can register any store of its own.

  • void Set(long ownerId, in TravelLocation bind)

    Stores the player's bind point (overwrites).

  • bool TryGet(long ownerId, out TravelLocation bind)

    The player's stored bind point, if any.

IBindPolicy provider

The game's bind rule: may THIS player bind HERE, now? Proximity to a shrine/inn/NPC, zone whitelists, cooldowns — all the game's vocabulary. The default allows everywhere (a checkpoint game); register yours BEFORE AddCrossplayTravel (a TryAdd seam).

  • bool AllowSetBind(ISession session, WorldEntity entity, out ushort reasonCode)

    True when binding at the entity's current spot is allowed. On refusal set reasonCode (0 falls back to BindRefused).

IRespawnPolicy provider

The game's respawn gate: WHEN may this player respawn? The piece has no death concept — a depleted health stat, a "stuck" button, a surrender flow are all the game's. The DEFAULT REFUSES every client-initiated respawn (an alive player must never free-teleport home); the Hosting composition bridges the Stats depletion state when configured, or register your own BEFORE AddCrossplayTravel (a TryAdd seam). Server code calling TryRespawn passes the same gate — one rule, both paths.

  • bool AllowRespawn(ISession session, WorldEntity entity, out ushort reasonCode)

    True when respawning is allowed right now. On refusal set reasonCode (0 falls back to RespawnRefused).

ITravelMover provider

HOW an entity actually moves — the seam that keeps Travel off the Movement piece (a sibling, never a reference). The default rides World alone: cross-zone = MoveToZone, same-zone = SetPosition. When the Movement piece is composed, the Hosting bridge substitutes its prediction-aware Teleport for same-zone player snaps. Register yours BEFORE AddCrossplayTravel (a TryAdd seam).

  • bool TryMove(ISession session, WorldEntity entity, in TravelLocation location)

    Moves a live entity to a location. False = the world refused (bad zone, gone entity).

IWarpPolicy provider

The game's warp rule: may THIS player take THIS destination now? Costs, level gates, quest unlocks — the game's vocabulary (charge through your Economy in your implementation). The default allows every CONFIGURED destination; register yours BEFORE AddCrossplayTravel.

  • bool AllowWarp(ISession session, WorldEntity entity, ushort destinationId, out ushort reasonCode)

    True when the trip is allowed. On refusal set reasonCode (0 falls back to WarpRefused).

Services you call 1

Crossplay implements these. Resolve them from DI and call them from your own systems.

ITravelService

Server-side Travel API — what the wire handlers call, and what game code calls directly (a death flow respawning a player, a dialog action warping one, a checkpoint trigger binding one — same gates, same moves, no wire needed).

  • event Action<long, TravelLocation> Respawned

    Raised after a COMMITTED respawn move with (owner id, resolved location) — the composition hook for invulnerability windows, analytics, camera fades…

  • bool TryRespawn(ISession session, out ushort reasonCode)

    Respawns the player: IRespawnPolicy gate → the ordered strategy chain resolves WHERE → the mover snaps them there. A refused respawn moves nothing.

  • bool TrySetBind(ISession session, out ushort reasonCode)

    Stores the player's bind point at their current spot (gated by IBindPolicy).

  • bool TryWarp(ISession session, ushort destinationId, out ushort reasonCode)

    Warps the player to a configured destination (gated by IWarpPolicy).

  • event Action<long, ushort> Warped

    Raised after a COMMITTED warp with (owner id, destination id).

Configuration 1

Every tunable lives in an options object — there are no magic numbers to hunt for.

TravelOptions

Configurable travel parameters (defaults here; never inlined in logic).

  • Dictionary<ushort, TravelLocation> Destinations { get; set; }

    The warp table, keyed by game-defined destination id. Empty (the default) leaves warping inert: every request answers UnknownDestination.

  • List<string> RespawnChain { get; set; }

    The ORDERED respawn strategy chain, by strategy name — the first that resolves wins. Built-ins: "bind" (the player's stored bind point) and "zone" (nearest configured zone point); games register their own named IRespawnStrategy and list it here (e.g. ["squad-leader", "bind", "zone"]).

  • Dictionary<string, List<TravelLocation>> ZoneRespawnPoints { get; set; }

    Per-zone respawn points for the built-in "zone" strategy — the NEAREST one to where the entity stands wins (graveyards, checkpoints, arena corners; the meaning is the game's). Key = zone key; empty = the strategy never resolves.

Wire messages 6

The protocol this piece speaks. Ids are allocated per piece so they can never collide.

5901 SetBindRequest

Client → server: "bind here". WHERE binding is allowed (a shrine, an inn, an NPC, a checkpoint trigger) is 100% the game's — its bind policy validates; the piece stores numbers.

5902 SetBindResponse

Server → client: the bind verdict.

5903 RespawnRequest

Client → server: "respawn me". WHEN this is allowed (dead, stuck, surrendered…) is the game's respawn policy; WHERE is the server's ordered strategy chain (bind → zone point → …).

5904 RespawnResponse

Server → client: the respawn verdict (the actual move replicates through the world wire).

5905 WarpRequest

Client → server: "warp me to destination X" — a key into the server's warp table. What a destination IS (a gate, a menu button, a spell) is the game's vocabulary.

5906 WarpResponse

Server → client: the warp verdict.

Unity services you inject 1

Crossplay binds these in the client context. Inject and call them from your own MonoBehaviours and presenters.

ITravelClient

The Travel piece, client side: bind your recall point HERE, request a respawn (the server resolves WHERE through its strategy chain), warp to a configured destination. The moves themselves replicate through the ordinary World/Movement wires — these are just the verbs and their private verdicts. Refusals carry a TravelNotificationCodes value.

  • void SetBind()

    Bind my respawn/recall point at my current spot (the game's bind policy validates).

  • void Respawn()

    Ask to respawn (allowed only in a game-defined respawnable state — e.g. dead).

  • void Warp(ushort destinationId)

    Travel to a configured destination.

  • event Action<SetBindResponse> BindResultReceived

    The server's verdict on your bind.

  • event Action<RespawnResponse> RespawnResultReceived

    The server's verdict on your respawn.

  • event Action<WarpResponse> WarpResultReceived

    The server's verdict on your warp.