Storylet Studio

Monitoring the StoryletEngine Server

The StoryletEngine Server management UI lives at server.storylet.studio. It's where operators register applications, manage bundles (stage / promote / rollback / retire), watch live sessions, and - when permitted - step in.

This page covers the operator flow. The author flow (staging from the Publish page) lives at Publishing to the StoryletEngine Server.

Who sees what. The management UI is gated by your role in the organisation. Anyone with a server role sees the live sessions and monitoring; a Producer can run and intervene in sessions, while creating apps, promoting bundles, and issuing device keys need a Manager. Controls you don't have the role for are hidden. See Members and Roles.

Signing in

Open server.storylet.studio → click Sign in with Google. You're in if you're a member of an organisation that has the server, and what you can do there comes down to your server role (see Members and Roles). Sign-in is shared with the other Storylet Studio sites, so if you're already signed in to the web app you'll usually be straight through.

If you can't get in, or the controls you expect are missing, ask your organisation's administrator to check your membership and roles.

The Applications list

Landing page. Lists every app registered in your org, with a status pill, the current live bundle version, and the number of currently-live sessions.

Click an app to open its detail page.

Creating an app

An app on the server is one deployment site running your storyworld - one venue, one street-game run, one cloud-game backend - not one device. Every device at that site (kiosks, IoT buttons, phones scanning a QR code, web frontends) lives under the same app and plays into the same shared storyworld. You only need a fresh app when you have a fresh site.

Top-right New application. You'll be asked for:

Field What it means
Name Friendly label for the site, not for a single device. "Lobby installation - Venue A" beats "kiosk-1".
Storyworld Pick from your org's published storyworlds (anything you've ever staged a bundle for, plus existing storyworld ids visible to the org).
Admin overrides allowed Off by default. When on, operators with org admin keys can write state on live sessions from the Inspector - set a property, advance the turn, force a play, restore a snapshot. Per-app safety flag. Audit-logged in either case.
Monitor subscribers max Cap on how many live monitors can watch one session at the same time. Default 8.
Rate limits Per-app caps on sessions/hour and calls/minute. Defaults are conservative; bump for high-throughput installations.

On create, the server generates a fresh app API key (sk_app_*). The plaintext is shown once, with a Copy button. Save it now - there's no "show me the key again" recovery; lose it and you'll have to rotate to a new one. This is the site-wide credential: every trusted device at the site (kiosks, IoT controllers, dedicated displays) carries the same key. Ad-hoc joiners (phones scanning a QR code) don't need this key directly - they mint short-lived session join tokens from a trusted device that holds it.

Rotating an app key

Open the app's detail page → Rotate API key. This generates a fresh sk_app_*, replaces the stored hash, and invalidates the previous key immediately. There's no overlap window in v1 - plan a brief downtime to update the device(s), or stage a temporary second app if you need rolling rotation.

The Application detail page

Two sections: Bundles and Sessions.

Bundles table

Every bundle ever published for this app, newest first. The columns:

Column Notes
Version Server-assigned, monotonic per app (v1, v2, …).
Status staged, live, or retired.
Published UTC timestamp.
By The author who published, by email.
Size KB.
Actions Context-sensitive buttons; see below.

The Actions column changes per row:

Bundle status Buttons
staged Promote  ·  Retire
live Retire (strong-confirm; leaves the app with no live bundle)
retired Roll back (this is "promote" under the hood)

Every action shows a confirmation dialog spelling out the impact. The one invariant to keep in mind:

Active sessions are never disturbed by promote / retire / rollback. They're locked to their bundle version at session-create time. Only new sessions pick up the new live bundle.

So promoting in the middle of a busy session is safe; new players coming in next get the new version while existing players finish on the old one.

Sessions table

Below Bundles: every session for this app. Columns: session id (clickable), bundle version, status (live / idle / ended), device count, last-active time.

The counts at the top of the section (live / idle / ended) update live - open this page in a tab while running a kiosk and you'll see new sessions appear and ended sessions drop out without a refresh.

Click a session to open the Inspector.

The Session Inspector

The Inspector is the operator's window into one live session: where the action is, what state the world is in, what just happened, and who's joined.

Layout:

+-----------------------------------------+----------------------------+
|                                         |  Properties                |
|                                         |  (live; grouped by scope)  |
|              Storymap                   |                            |
|       (Konva canvas; sites, zones,      +----------------------------+
|        backgrounds, pins; live hand-    |  Action log                |
|        count badges; current site       |  (reverse-chronological)   |
|        highlighted)                     |                            |
|                                         +----------------------------+
|                                         |  Site hands                |
|                                         |  (cards for each active    |
|                                         |   site's current hand)     |
+-----------------------------------------+----------------------------+
  Header strip: app name · bundle version · turn · current Act ·
                 device list · status · Save / Restore / End session

The storymap

The same visual language as the authoring tool's Storymap pages: zones as coloured polygons, sites as labelled markers, pins, backgrounds, annotations. Read-only: no edit handles, no drag. The map is rendered from the bundle the session started with, not the current live bundle, so it matches what the runtime is actually playing.

Live overlays on top:

  • Hand-count badge on every active site, updated on every play / prime / draw.
  • Currently-active site highlight - a ring around the site the most recently played storylet drew from.
  • Device-presence markers - a dot per joined device at the zone/site they joined.

Click any site to scroll its row into view in the Site Hands panel.

Properties panel

Every property in the session, grouped by scope (world / story / deck / act / zone / site). Live; updates whenever the session's state changes.

Read-only when the app has adminOverridesAllowed: false (the default). When the flag is on for this app, an edit pencil appears next to each value - click to type, then save. Validation errors from the engine (type mismatch, out-of-enum) appear in-place.

Action log

Reverse-chronological list of action-log entries: drawn, evicted, played, actChange, hostSet. Live; new entries appear at the top as they happen. Filter pills above the list narrow to one or more kinds.

Each entry shows the storylet / outcome involved, the turn value at the time, and any changes that came from an operator override (shown distinctly from normal play).

Site hands

One card per currently-active site, showing the storylets currently in that site's hand. Each storylet shows its title and source deck. Live; updates as hands change.

When adminOverridesAllowed: true, each card gets a Force play action: pick an outcome, optionally pick a turn advance, confirm. Same code path as a player-side play, but audit-logged as an operator action.

Header strip and session actions

Top of the page:

  • App name + bundle version (with a "view bundle" link to the Storage URL).
  • Turn value and current Act, updated live.
  • Device list - every joined device, with its role and join time.
  • Status pill - live / idle / ended.

Actions on the right:

Action Permission Notes
Save state any operator (read-only) Downloads a .storyworldstate file. See Save / restore.
Restore state adminOverridesAllowed: true Uploads a .storyworldstate and replaces the live session's context. Destructive.
Clone session from snapshot any operator Starts a fresh session pre-populated from an uploaded snapshot. No override needed.
End session any operator Closes the session. Existing snapshots are kept.

Save / restore world state

The Inspector gives you three things you can do with a session's state. All three speak the same .storyworldstate file format the Unreal port, the Unity port, and the authoring tool's simulate flow already use.

Save state

Click Save state in the header strip. The browser downloads a .storyworldstate file. You pick a filename.

This is purely read-only: it captures the session's current WorldContext (every property, every site's slots, the action log, the turn) and emits a file. No admin override needed. Handy for:

  • Snapshotting a bug repro mid-session so you can hand it to an author.
  • Capturing a known-good "demo state" to reuse later.
  • Backing up a long-running installation now and again.

Restore into this session

Click Restore state → pick a .storyworldstate file → confirm the "this will replace the current state" dialog. The session's world is replaced; joined devices see the change immediately; an audit-log entry records the operator and the file.

Requires adminOverridesAllowed: true on the app. Strong-confirm, because this is destructive - the previous state is gone unless you saved it first.

Clone session from saved state

From the app's Sessions section or from inside the Inspector ("Clone session from this snapshot"): upload a .storyworldstate, get back a fresh session id pre-populated from the snapshot.

Doesn't need the admin override flag - it's a new session, not a destructive edit. Handy for QA repro: "here's the state when the bug fires; spin up a fresh session at that point and try to make it happen again." The cloned session has its own session id and its own join URL.

Cross-source compatibility: state files saved from the simulate panel in the authoring tool, from the Unreal port, or from the Unity port can all be restored into a server session (and the other way round) as long as the storyworldId and storyletsVersion match. Mismatches return bundle_mismatch and the upload is rejected.

Admin override safety

The single per-app safety flag is adminOverridesAllowed. Off by default. When off, every operator panel in the Inspector is read-only and the override controls aren't rendered. When on:

  • Properties panel gains inline edit.
  • Action log gains an Advance turn button.
  • Site hands gain Force play.
  • Restore-into-this-session is available from the header strip.

Every override write produces an auditLog entry with the operator's email and the precise diff applied. Override actions also show up in the session's action log as hostSet entries - visually distinct from normal play, so a player-facing log (if you have one) reads correctly as "this was operator intervention, not the player".

The flag is meant for venues where operators sit alongside a live game and may need to nudge things during play (a stuck slot, a frozen player, a demo that needs to fast-forward). For installations that should be hands-off - a public exhibit, a game running unattended - leave the flag off.

Multiplayer sessions

If a storyworld is multiplayer (see Multiplayer Storyworlds), a single session is shared by many players at once, each on their own device. Here's what that changes for you as the operator:

  • One session, many players. The session you see in the list is the shared playthrough every joined device plays into. A shared property changed by one player is immediately true for all of them; each player also has their own personal state and their own turn.
  • Players are minted as they arrive. A device that joins with no prior identity gets a fresh anonymous player automatically. These anonymous players are PII-free - just an opaque handle. (Letting a player return later and pick up their durable @player state - via a card, a QR code, or an account - is on the roadmap, not here yet.)
  • @player state is remembered between sessions. A returning bound player resumes their durable @player values from a previous session; a fresh player starts from the declared defaults. You can administer this remembered state out of session: the app's page has a Returning players (@player) panel listing players who have durable state (by friendly name / id), where you can view a player's @player values, edit them (admin overrides on), or forget a player's state entirely (right-to-be-forgotten). An edit to a player who's currently in a live session shows up there immediately.
  • @system is your control surface. A multiplayer storyworld's installation-wide @system values (the venue's long-term state and control knobs - "trolls defeated this week", "we are now in the evening phase") persist across every session of the app. You - or an external system you connect (a show scheduler, building control, an IoT trigger) - can read and set them from outside the story. A property the author marked Operator & external only can be set only this way; the story never writes it.

Setting @system from the monitor UI. Multiplayer apps now show an Installation control (@system) panel - on the App page (out of session) and in the Session Inspector (in session). It lists each declared @system property with its current durable value and a badge showing who may write it, and lets a Producer set any operator-writable value inline. A change applies to live sessions immediately and persists across every future session. A property the author marked Operator & external only is set here; one the story owns (outcomes-only) is shown read-only. Every write is audit-logged.

The same values are also reachable programmatically over the server's API - an org-admin key (operator) or the app's own API key (an external control system: a show scheduler, building control, an IoT trigger) can read and write them through your own integration or the JavaScript server-client library.

Seeing the players. A multiplayer session's Inspector also shows a Players panel: every player in the session, listed by a friendly auto-name (friendly-fox - display only, since anonymous players have no real name yet), with their turn, the site they last acted at, and how long ago. Note that's where they last played, not a live position - the server only learns where a player is when they take a turn, so the panel never claims real-time location. Click a player to open their lens: their own properties (their personal and durable @player values merged with the shared world state) and their personal action log. When the app allows admin overrides and you hold the Producer role, you can edit that one player's values inline - their durable @player values and their personal world/scoped values - to unstick a single player whose game has gone wrong. (Shared state stays read-only in the lens: edit it in the Shared state panel, since it affects everyone; @system has its own panel.)

Editing shared state. For a multiplayer session the top-level Properties panel becomes a Shared state panel: the properties that have one value the whole session sees. When the app allows admin overrides and you hold the Producer role, you can edit these inline - an edit applies to every player at once (use it to open a gate for everyone, fix a stuck shared tally, and so on). @system has its own panel; durable @player and personal values can't be edited here.

Sessions lifecycle, at a glance

Status What it means
live Has at least one joined device and is being actively played.
idle Past the idle window (default 30 min) with no activity. In-memory state evicted, but the session id is still resolvable - the next call from any device re-hydrates it from snapshot.
ended Explicitly ended, or hit the max-lifetime cap (default 24h). Returns 410 to subsequent calls.

You can let an idle session sit indefinitely; it costs nothing in memory. End it explicitly when you're done with it (or want it off the live counts on dashboards).

Cross-references