Revision / Outdated Session Handling
Network Storage supports revision detection to help games manage version mismatches during live play. When a new game revision is published, clients running an ...
# Revision / Outdated Session Handling
Network Storage supports revision detection to help games manage version mismatches during live play. When a new game revision is published, clients running an older revision can be detected, warned, and optionally blocked from making changes.
## Overview
The revision system:
1. Tracks which s&box game revision each player is running
2. Compares it to the latest published revision
3. Returns status in every endpoint response via `_revisionStatus`
4. Supports two enforcement modes: **Allow Continue** and **Force Upgrade**
## Enforcement Modes
Configure the enforcement mode in **Project Settings > Revisions** on the sbox.cool dashboard.
### Allow Continue Mode
Players see a notification but can keep playing indefinitely. The popup shows:
- "NEW VERSION AVAILABLE" title (informational styling)
- "Continue Playing" as the primary action
- Optional "Create New Session" / "Join New Lobby" buttons below a divider
**Best for:** Casual games, single-player experiences, or games where interrupting sessions would be disruptive.
### Force Upgrade Mode
Players get a grace period countdown, then the server enforces the upgrade. The popup shows:
- "UPDATE REQUIRED" title (warning styling)
- Grace period countdown with consequence warning
- "Create New Session" / "Join New Lobby" buttons
- "Dismiss for now" link
**Best for:** Competitive games, games with real-time multiplayer, or when version consistency is critical.
#### Post-Grace Actions (Force Upgrade only)
When the grace period expires:
| Action | Effect |
|--------|--------|
| `block_writes` | Player can still read data but save/write endpoints return errors |
| `block_all` | All endpoint requests are blocked — game stops functioning |
## Server Configuration (Dashboard)
In **Project Settings > Revisions**:
| Setting | Description |
|---------|-------------|
| **Enforcement Mode** | `allow_continue` or `force_upgrade` |
| **Grace Period** | How long players can continue after a new version (Force Upgrade only) |
| **Post-Grace Action** | `block_writes` or `block_all` (Force Upgrade only) |
| **Show New Version Banner** | Display persistent banner (Allow Continue only) |
| **Show Update Options** | Whether to show Create/Join buttons in popup |
| **Show Popup Once** | Only show popup once per session |
| **Custom Message** | Optional message displayed to players |
## How Detection Works
### The `_revisionStatus` Response Block
Every endpoint response includes revision status:
```json
{
"ok": true,
"body": { ... },
"_revisionStatus": {
"isOutdatedRevision": true,
"playerRevision": 227325,
"currentRevision": 227400,
"enforcementMode": "allow_continue",
"graceRemainingMinutes": null,
"graceExpired": false,
"action": "warn",
"message": "A new version is available.",
"policy": {
"enforcementMode": "allow_continue",
"gracePeriodMinutes": null,
"postGraceAction": null,
"forceEndpointUpgrade": false,
"notifyMessage": null,
"showNewVersionBanner": true,
"showUpdateOptions": true,
"showPopupOnce": true
}
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| `isOutdatedRevision` | bool | True when running an older revision |
| `playerRevision` | number | The revision the client is running |
| `currentRevision` | number | The latest revision on the server |
| `enforcementMode` | string | `"force_upgrade"` or `"allow_continue"` |
| `graceRemainingMinutes` | number? | Minutes left in grace period (Force Upgrade) |
| `graceExpired` | bool | True when grace period has ended |
| `action` | string | Current action: `"warn"`, `"block_writes"`, `"block_all"` |
| `message` | string | Human-readable message for players |
| `policy` | object | Full policy settings from dashboard |
### Legacy `revision` Block (load-profile)
The `load-profile` endpoint also returns a `revision` block for backward compatibility:
```json
{
"revision": {
"currentRevisionId": 227325,
"latestRevisionId": 227400,
"revisionOutdated": true,
"graceSeconds": 0,
"graceEndsAtUnixSeconds": null,
"serverUnixSeconds": 1777855461
}
}
```
## Library Installation
Install **Network Storage by sbox.cool** from the s&box Library Manager.
The library is **optional** — you only need it if you want:
- The built-in popup UI
- Helper methods and events
- Automatic status tracking
Without the library, your game still receives `_revisionStatus` in every endpoint response. You can parse it yourself and build your own UI.
## Client Configuration
All client-side features are **opt-in**. The built-in UI is **disabled by default**.
### Option 1: Enable the Built-in Popup
```csharp
// Enable the default revision warning popup
NetworkStorage.RevisionSettings.ShowDefaultMessage = true;
// Optional: customize behavior
NetworkStorage.RevisionSettings.ShowOnlyOnce = true; // Show once per session (default)
NetworkStorage.RevisionSettings.AutoOpenOnOutdated = true; // Auto-open when detected (default)
NetworkStorage.RevisionSettings.AllowSpaceToClose = true; // SPACE key closes popup (default)
```
### Option 2: Custom UI via Events
```csharp
// Keep built-in UI disabled
NetworkStorage.RevisionSettings.ShowDefaultMessage = false;
// Subscribe to the revision event
NetworkStorage.OnRevisionOutdated += ( data ) =>
{
var mode = NetworkStoragePackageInfo.EnforcementMode;
if ( mode == RevisionEnforcementMode.AllowContinue )
{
// Gentle notification
MyUI.ShowBanner( "Update available", data.Message );
}
else
{
// Urgent warning with countdown
var mins = NetworkStoragePackageInfo.GraceRemainingMinutes;
MyUI.ShowWarning( $"Update required in {mins} minutes" );
}
};
```
### Option 3: Query Status Directly
```csharp
// Check status anytime
if ( NetworkStoragePackageInfo.IsOutdatedRevision )
{
var mode = NetworkStoragePackageInfo.EnforcementMode;
var expired = NetworkStoragePackageInfo.GraceExpired;
var action = NetworkStoragePackageInfo.RevisionAction;
var message = NetworkStoragePackageInfo.RevisionMessage;
// Handle as needed
}
```
### Disabling the Default UI
To completely disable the built-in popup and handle revision detection yourself:
```csharp
// Disable the built-in popup (this is the default, but be explicit)
NetworkStorage.RevisionSettings.ShowDefaultMessage = false;
// The revision system is still enabled - events still fire
NetworkStorage.OnRevisionOutdated += ( data ) =>
{
Log.Info( $"Player on revision {data.CurrentRevisionId}, server has {data.LatestRevisionId}" );
MyCustomUI.ShowOutdatedWarning( data );
};
// Check status anytime without events
if ( NetworkStoragePackageInfo.IsOutdatedRevision )
{
var mode = NetworkStoragePackageInfo.EnforcementMode;
var expired = NetworkStoragePackageInfo.GraceExpired;
}
```
To disable all revision detection (no events, no tracking):
```csharp
NetworkStorage.RevisionSettings.Enabled = false;
```
## Built-in UI Events
When using the built-in popup, subscribe to player actions:
```csharp
// Player clicked "Continue Playing" (Allow Continue mode)
NetworkStorageOutdatedUI.OnContinuePlaying += () =>
{
Log.Info( "Player continuing on old version" );
};
// Player clicked "Create New Session"
NetworkStorageOutdatedUI.OnCreateNewGame += () =>
{
Game.Disconnect();
CreateNewLobby();
};
// Player clicked "Join New Lobby"
NetworkStorageOutdatedUI.OnJoinNewLobby += () =>
{
ShowLobbyBrowser( filterToCurrentRevision: true );
};
// Player dismissed popup (Force Upgrade mode)
NetworkStorageOutdatedUI.OnDismiss += () =>
{
Log.Info( "Warning dismissed" );
};
```
## Programmatic UI Control
```csharp
// Open popup manually (requires parent panel)
NetworkStorageOutdatedUI.Open( myHudPanel );
// Close popup
NetworkStorageOutdatedUI.Close();
// Check visibility
if ( NetworkStorageOutdatedUI.IsOpen ) { ... }
// Reset popup state - allows popup to show again even with ShowPopupOnce
// Useful when starting a new game session or after reconnecting
NetworkStorageOutdatedUI.ResetState();
```
## Available Status Properties
Query `NetworkStoragePackageInfo` for current state:
| Property | Type | Description |
|----------|------|-------------|
| `IsOutdatedRevision` | bool | Running an older revision |
| `EnforcementMode` | RevisionEnforcementMode | `ForceUpgrade` or `AllowContinue` |
| `GraceRemainingMinutes` | int? | Minutes left in grace |
| `GraceExpired` | bool | Grace period ended |
| `RevisionAction` | string | `"warn"`, `"block_writes"`, `"block_all"` |
| `RevisionMessage` | string | Server message |
| `CurrentRevisionId` | long? | Running revision |
| `ServerCurrentRevision` | long? | Latest revision |
| `PolicyShowUpdateOptions` | bool | Show update buttons |
| `PolicyShowPopupOnce` | bool | One popup per session |
## Event Data
`OnRevisionOutdated` provides `RevisionOutdatedData`:
| Property | Type | Description |
|----------|------|-------------|
| `CurrentRevisionId` | long | Running revision |
| `LatestRevisionId` | long | Server's latest |
| `RevisionOutdated` | bool | Is outdated |
| `EnforcementMode` | RevisionEnforcementMode | Mode from server |
| `GraceExpired` | bool | Grace ended |
| `GraceRemainingMinutes` | int? | Minutes remaining |
| `Message` | string | Server message |
| `Action` | string | Current action |
| `ShowUpdateOptions` | bool | Show buttons |
| `ShowPopupOnce` | bool | Once per session |
| `TimeRemaining` | int | Seconds until grace expires |
| `IsGraceExpired` | bool | Computed expiry check |
| `Source` | string | `"load-profile"`, `"manual-test"`, `"lobby"` |
| `Reason` | RevisionOutdatedReason | Why event fired |
## Testing
Test revision UI without publishing a new version:
```csharp
// Show UI + fire events (bypasses ShowOnlyOnce)
NetworkStorage.TestShowRevisionOutdatedMessage( parentPanel );
// Fire event only (test hooks without UI)
NetworkStorage.TestFireRevisionOutdated();
```
## Lobby Metadata
Stamp revision info when creating lobbies:
```csharp
var meta = NetworkStorage.BuildLobbyMetadata(
migrationRevision: null,
sourceLobbyId: null
);
lobby.SetMetadata( meta );
```
Check lobby freshness:
```csharp
bool isFresh = NetworkStorage.IsLobbyOnCurrentRevision( metadata );
bool isStale = NetworkStorage.IsLobbyStale( metadata );
```
Metadata keys:
| Key | Description |
|-----|-------------|
| `ns_rev` | Current revision ID |
| `ns_stale` | `1` if outdated, `0` if current |
| `ns_grace_end` | Grace period end timestamp |
| `ns_mig` | `1` if supports migration |
| `ns_mig_rev` | Migration target revision |
| `ns_src` | Source lobby ID |
## Full Example
```csharp
public partial class MyGame : GameManager
{
public override void ClientJoined( IClient cl )
{
base.ClientJoined( cl );
if ( cl.IsHost )
{
InitializeNetworkStorage();
}
}
private void InitializeNetworkStorage()
{
NetworkStorage.Configure( "my-project-id", "sbox_ns_mykey" );
// Enable built-in UI
NetworkStorage.RevisionSettings.ShowDefaultMessage = true;
// Handle player actions
NetworkStorageOutdatedUI.OnCreateNewGame += () =>
{
SaveProgress();
Game.Disconnect();
};
NetworkStorageOutdatedUI.OnContinuePlaying += () =>
{
ShowPersistentReminder();
};
}
}
```
## Security Notes
- **Never trust client-reported revision status.** The server computes the authoritative value.
- Enforcement is server-side. Modified clients cannot bypass `block_writes` or `block_all`.
- The `_revisionStatus` block in responses is the source of truth.
## Backward Compatibility
- If `enforcementMode` is missing from the response, defaults to `force_upgrade`
- The legacy `revision` block in `load-profile` remains for older library versions
- Existing `allow_readonly` post-grace action maps to `allow_continue` mode
## Warnings
- Test thoroughly before enabling Force Upgrade in production
- `block_all` locks players out entirely — prefer `block_writes` for graceful degradation
- Sync tool must push package info before revision detection works
- All client features are opt-in; nothing is enabled by default
---
## Quick Reference
### Settings (`NetworkStorage.RevisionSettings`)
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `Enabled` | bool | `true` | Enable/disable all revision detection |
| `ShowDefaultMessage` | bool | `false` | Show built-in popup UI |
| `ShowOnlyOnce` | bool | `true` | Show popup once per session |
| `AutoOpenOnOutdated` | bool | `true` | Auto-open popup when outdated detected |
| `AllowSpaceToClose` | bool | `true` | SPACE key closes popup |
### Events
| Event | Fires When |
|-------|------------|
| `NetworkStorage.OnRevisionOutdated` | Server reports player is on outdated revision |
| `NetworkStorageOutdatedUI.OnContinuePlaying` | Player clicks "Continue Playing" |
| `NetworkStorageOutdatedUI.OnCreateNewGame` | Player clicks "Create New Session" |
| `NetworkStorageOutdatedUI.OnJoinNewLobby` | Player clicks "Join New Lobby" |
| `NetworkStorageOutdatedUI.OnDismiss` | Player dismisses popup |
### UI Control (`NetworkStorageOutdatedUI`)
| Method/Property | Description |
|-----------------|-------------|
| `RootPanel` | Set parent panel for auto-created popup |
| `IsOpen` | Check if popup is visible |
| `Open(parent)` | Show popup manually |
| `Close()` | Hide popup |
| `ResetState()` | Reset state (allows re-showing) |
### Status (`NetworkStoragePackageInfo`)
| Property | Type | Description |
|----------|------|-------------|
| `IsOutdatedRevision` | bool | Running older revision |
| `EnforcementMode` | RevisionEnforcementMode | `ForceUpgrade` or `AllowContinue` |
| `GraceRemainingMinutes` | int? | Minutes left in grace |
| `GraceExpired` | bool | Grace period ended |
| `RevisionAction` | string | Current action |
| `RevisionMessage` | string | Server message |