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).
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> RespawnedRaised 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> WarpedRaised 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.
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.
SetBindResponse Server → client: the bind verdict.
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 → …).
RespawnResponse Server → client: the respawn verdict (the actual move replicates through the world wire).
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.
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> BindResultReceivedThe server's verdict on your bind.
-
event Action<RespawnResponse> RespawnResultReceivedThe server's verdict on your respawn.
-
event Action<WarpResponse> WarpResultReceivedThe server's verdict on your warp.