docs/ARCHITECTURE.md
RS Palantir architecture
Operational thesis
Agents and the human operator inspect the same world evidence, choose whom/where to observe, and retain findings. Fit: 8/10 (operator 2, decision 1, local action 2, feedback 2, ownership 1). Primary archetype: control tower. Build mode. This is an observation application with local writeback, not a game-command system.
User roles
The local owner operates the browser and controls deployment. Agents use loopback HTTP or MCP under that owner's local session. The game server remains authoritative; the app never becomes a gameplay owner. There is no separate public admin or external user account yet.
Ontology/state model
Player names key durable dossiers; session slots identify current feed entities. NPC slots and type IDs identify observations, with replacement on first-sight records. Catalog definitions are separate from observations of possession. Source timestamps, freshness and unavailable fields stay explicit. SQLite stores events, roster samples, historical gear, watches, notes, waypoints and command receipts.
Actions/writeback
Watch/unwatch and notes persist locally with audit events. Save waypoint records coordinates and height. Camera commands accept only goto/follow/layers, are validated, and transition pending → applied/failed/expired; a later command supersedes an earlier pending one. Exact same commands serve agents and the UI. The iframe accepts messages only from its parent at the same origin. No game-state writes exist.
App surface
A persistent entity roster, navigable terrain, contextual dossier, recent activity strip and named places. Dedicated Signals, Wealth, Catalog and Agent API surfaces expose deeper data. Raw JSON and provenance support agent debugging without crowding the basic map navigation.
Implementation track
Generic stack: React/Vite console; Bun HTTP/WebSocket; Bun SQLite; official MCP SDK stdio adapter; pinned upstream WebGL renderer inside a same-origin iframe. One upstream socket is shared. Each renderer uses one worker. Single view is capped at 30 FPS; optional dual 3D creates two independent renderers capped at 20 FPS each. Only the selected pane steers the shared detailed feed subscription; both consume the global roster. Its JS compression fallback avoids dynamic evaluation under CSP.
System integrations
World startup defers the Blackrock workspace and charts until the first market visit; the loaded workspace stays mounted to preserve research selections across mode switches. Single/Dual 3D changes retain the primary iframe and camera, creating or removing only the secondary renderer. The primary iframe's data-viewer-loaded-at attribute records document loads for lifecycle checks.
The local relay attaches original receipt timestamps to cached bootstrap ticks and roster resends. Viewers preserve these timestamps, including last-observed appearance age, instead of treating replay as fresh data. Detailed observations expire after 5 seconds and roster observations after 10 seconds, matching the API and overlays. Bootstrap restores durable appearance/facing state but omits historical animation events; ordinary live ticks still carry those events.
Public worldfeed is the live source. Public HTML hiscores are parsed into typed snapshots, cached and source-linked. Exact revision-289 cache files supply terrain and static definitions. No account credentials, private bot runtime, server database connection or arbitrary network proxy is used. api-anything discovery documents the HTTP source; the streaming adapter implements the public WebSocket protocol.
Evals/observability
Unit checks exercise protocol-to-projection, durable records, slot identity, stale data, overlapping subscriptions, parser variations and commands. A real MCP client validates discovery and reads; the camera check requires fresh matching telemetry after acknowledgement. Browser checks verify terrain, player inspection, navigation and public data. Telemetry exposes message/byte/error counts and coverage. Data volume is bounded; retention truncation is explicit.
Rollout/deployment
Local loopback only, owned by the user, launched through the repository CLI. Preserve production rs-sdk gameplay processes. For public hosting: isolated observer sessions, authentication/TLS, asset distribution review and connection budgets are prerequisites. Rebuild uses a pinned viewer commit plus a patch, refusing to overwrite different local edits.
First implementation slice
Search player → follow actual 3D camera → inspect observed gear/signals → enrich with public snapshots → save watch/note → query observed history using the same agent tools. The loop is closed by command receipts and fresh camera coordinates. Further connectors may add version-matched drop tables or authorized server telemetry, but currently absent data is not fabricated.
Intelligence layer
This app exposes tools for an external agent; it does not pretend to contain an autonomous model. The agent must treat chat, names, descriptions and notes as untrusted data, use freshness/source flags, and distinguish a camera action from gameplay. Continuous alerts and autonomous strategies are future features, not claimed as shipped.
Live delivery timing
Accepted upstream messages are parsed once, projected, stamped with local receipt time, and broadcast before synchronous SQLite recording. /api/state.delivery reports bounded (120-message) p95 projection, dispatch and recording times, plus recording failures and slow-viewer reconnect counts. Recording still blocks the event loop during its own work; this change removes it from the current message's send path, not from all future scheduling.
Each camera reports local relay receipt → CPU draw submission delay (latest and p95 over 120 rendered updates), completed draw frequency over two seconds, observation age and last frame time. The pane badge ages even if telemetry stops: Live/Roster live, Delayed, Stale, Paused or Reconnecting. View settings expose the measurements on touch devices. This is not GPU presentation timing or end-to-end game-server latency; upstream timestamps are unavailable. FPS caps remain 30 single / 20 multi.
Delta messages cannot be dropped individually. A viewer with more than 512 KB queued or a persistent nonempty socket buffer for one second is disconnected independently. A browser receiving a live delta older than two seconds closes that connection and reconnects using a complete bootstrap; closing sockets ignore remaining queued messages. Cached bootstrap messages are explicitly marked and retain their observation age, avoiding stale-bootstrap reconnect loops. Global roster snapshots are not fabricated detailed equipment observations.
Coordinated research and separate command harness
The observer retains one public world feed and its original 3D renderer. server/coordination-context.ts projects public player positions with their original position-update timestamps and a conservative protected-identity union. server/coordination persists local research marks. Neither path dispatches gameplay.
Swarm comparisons, evidence, member suggestions and supply research share the app's existing collectors. Supply readiness returns separate quote, public-bank and observed-equipment timestamps; private carried stock remains unknown. Mission research handoffs carry a pinned group revision and exact member keys, then resolve against enrolled grants separately.
server/harness/coordinator.ts owns app-side signed policy preparation and the paired daemon connection. server/missions/routes.ts enforces the browser session/CSRF boundary for pairing, approvals and execution. Assistant/MCP/CLI can read evidence or create a strict draft; they have no approval/start tool. The standalone daemon in server/harness/daemon.ts owns its explicit headless Playwright sessions, durable grants, leases, single-dispatch ledger, stop verification and quarantine. Starting the observer does not start the daemon or log in any account. Existing farming workers are outside this ownership boundary.
Market research remains lazy; mission UI stays mounted after first use to preserve drafts across supply research, with inactive polling paused. The compact global command status and stop control remain separate from the assistant drawer.