docs/PUBLIC-WEB-PREVIEW.md

Public website viewer export

The separate rs-sdk/whop-site repository owns the hosted /demo page. This app repository owns maintained viewer source, tests and the allowlisted export. The private RS-SDK checkout is not an input.

After viewer changes, update patches/rs-world.patch and run:

sh
1pnpm run viewer:build
2pnpm run check
3# Commit reviewed source before export so provenance points to a reproducible revision.
4bun scripts/export-web-preview.ts /path/to/bots-whop-site

The exporter verifies the source remote and destination Whop linkage. Only static renderer assets (excluding source maps), the named public rs-sdk-289 cache files, notices, all 117 public location presets, and three browser-safe shared modules (feed snapshots, the public feed buffer and monitor arrangement helpers) are copied. The exporter rewrites the gzip dependency’s build-machine fallback URL to its bundled WASM asset and sets the default feed to the public server. Export fails if local paths or endpoints remain in JavaScript. The generated provenance records SHA256 hashes, source revision, upstream revision, patch hash and shared-module hashes. No server, bot.env, private roster, account configuration, observation database or gameplay runtime is copied.

An embedded viewer with public=1 and the exact public wss://rs-sdk-demo.fly.dev/worldfeed URL sends rs-watch-public-state once a second to its same-origin parent. The projection contains validated public player names, online count, NPC count and roster/detail timestamps. Stale or disconnected feeds suppress names/counts. Regular app embeddings do not emit this message.

The existing origin-and-parent-window message guard applies to the new rs-watch-layer command. Its only accepted values are surface and dungeons; it manipulates the spectator camera using FlightLayers. It does not run gameplay commands. Every visitor has one public feed socket and observation area across up to nine cameras. The regular application retains its existing shared detail feed.

The website must validate origin and source window, show freshness and label sample data. Pause always closes the socket and unmounts all frames. Default sessions also stop on backgrounding or after ten minutes; an explicit Broadcast session opts out of those two pauses for recording. It must never relay the private app. WebGL2 is required; upstream availability and visitor capacity are outside the static renderer's guarantees.

Shared browser transport and overlays

The opt-in public=1&transport=parent transport is enabled only for an embedded viewer using the fixed public URL. The website owns one socket and sends validated feed messages to same-origin iframe windows. Frames can request subscriptions, but only the selected camera may move the single observation area. No gameplay messages are forwarded. Other cameras retain global player positions and show position-only coverage outside that area.

shared/feed-snapshot.ts is shared with the existing app relay. Late-mounted cameras receive durable entity appearance/position fields with original observation timestamps, never replayed chat or animation events. shared/public-feed-buffer.ts bounds frames, rosters, entity maps and subscriptions, and clears state on reconnect. The maintained viewer reuses MovementSamples and research overlays: 15-second sampling, five-minute trails, no connections across teleports, floor changes or stale gaps.

The website owns Goondelf and Goo-numbered-name shortcuts, camera layout, scene links and studio/OBS controls. Scene links store targets and overlay preferences, not credentials or recorded positions. Normal app embeddings retain their existing transport.

Public walls with more than four cameras use a ten-FPS renderer budget per pane; smaller walls retain the existing twenty-FPS multi-pane budget. Mobile stacks cameras vertically.