# Agent runtime and viewport performance

Measured on 2026-08-23 with the primary account in Broceliande, a 1024 m x
1024 m varregion, using the default 64 m view distance. The rendered scene had
211 objects, 427,253 triangles, and five unavailable grid textures. All timing
numbers below are optimized release builds.

## Runtime ownership

- `metacrate-grid-world` owns native OpenSim/Second Life event subscriptions,
  distance filtering, attachment world transforms, terse movement application,
  event-drop accounting, and immutable published world snapshots.
- `metacrate-game-loop` owns the fixed update schedule, renderer lifetime,
  persistent GPU scene, fixed-rate viewport loop, completed-frame buffers, and
  timing signals. Readers receive an immutable `Arc<[u8]>`; they never read a
  render target while Bevy writes it.
- `metacrate-rendering-wgpu` owns Bevy/wgpu resources and supports separate
  scene loading and camera/frame rendering. Shared immutable scene arrays cross
  the renderer thread without copying their contents.
- `metacrate-grid-agent` supplies policy, conversation, tools, and lifecycle
  triggers. It consumes published world/viewport state instead of synchronizing
  render buffers or individual native event channels.

Grid callbacks never perform asset I/O or render work. The world loop drains a
bounded 4,096-event queue in batches, coalesces dirty IDs, and publishes only
complete snapshots. Object, avatar, attachment, and terrain admission uses the
configured view distance; 64 m is the default. The live reference contained
280 visible prims, one avatar, and 51 nearby terrain patches, with zero dropped
events.

## Readiness

Transport connection is not agent readiness. A generation remains degraded and
chat, tools, movement, and autonomous behavior remain fenced while the terminal
reports:

```text
INITIALIZING generation=N waiting_for=world_state,viewport target_fps=10
```

The framework gate opens only after the region-scoped world state exists, the
first scene/assets have converged, and at least two complete viewport frames
have been published after the last missed deadline, within the frame budget,
and without render errors. One-time warm-up misses are reported separately and
do not contaminate the post-`READY` stability window. The terminal then emits
`INITIALIZED ...` followed by one unambiguous `READY ...` line when the session
and agent services are released. The default readiness allowance is two
minutes; the measured scene convergence window was about 30.05 seconds.

## Measured steady state

| Phase | Measured time |
|---|---:|
| Bevy/wgpu initialization | 476 ms |
| First persistent GPU scene load and frame | 1.02 s |
| Cached complete scene refresh | 86 ms |
| Stable warm render plus coherent CPU readback | 31–36 ms |
| JPEG encoding on demand | 37 ms |
| Repeated capture using the already published viewport, including JPEG | 45 ms |

The viewport uses a 90 ms scheduling interval, providing margin above the
minimum 10 FPS requirement. The live two-second soak completed 22 frames at
10.99 FPS with zero missed deadlines and zero render errors. JPEG encoding and
LLM transfer are not in this loop.

A cached full-scene refresh completed in 86 ms, inside the 90 ms interval:
validation 1.08 ms, shared command handoff 0.001 ms, texture cache sync 30.47
ms, mesh cache sync 15.21 ms, scene setup 0.47 ms, and render/readback 35.70 ms.
Unchanged frames skip validation, command transfer, texture sync, and mesh sync.

## Startup work

| Phase | Reference time | Scheduling |
|---|---:|---|
| Client owner initialization | 2 ms | Startup thread |
| Grid login | 3.37 s | Parallel with renderer initialization |
| Bevy/wgpu initialization | 0.49 s | Completed before agent readiness |
| Initial world/asset convergence | 30.05 s | Asynchronous Grid and asset workers |
| First persistent GPU scene and frame | 1.02 s | Before the readiness gate opens |

The slower first start is intentional: the agent does not claim readiness until
it can provide stable world and visual state. Raw immutable assets persist in
the configured 2 GiB LRU under the platform cache directory
(`$HOME/.cache/metacrate` on Linux), so later starts reuse them.

## Frame publication and future GUI path

The current agent path continuously renders and publishes the latest complete
RGBA frame. A requested snapshot reads that immutable frame and performs JPEG
encoding independently. Camera or scene submissions replace stale pending
input; consumers never block the viewport loop.

The current Bevy screenshot API still performs synchronous CPU readback, which
accounts for most of the 31–36 ms warm frame cost. This meets the current
headless-agent target, but a GUI viewer should present the GPU target directly.
For higher-rate CPU capture, use a ring of three staging buffers: render, enqueue
GPU copy, map asynchronously, and publish only after completion. If all staging
buffers are occupied, skip that readback instead of stalling rendering.

## Instrumentation and observed limits

Hot paths record monotonic timestamps and fixed-cardinality counters only.
World event drops, completed frames, missed deadlines, render errors, and last
render duration are available without formatting or I/O on the loop threads.

Observed limiting factors:

- Initial scene/asset convergence, about 30 seconds, dominates readiness.
- First GPU admission is about one second; it occurs before `READY`.
- A cached full-scene refresh is 86 ms and is currently the closest operation to
  the 90 ms frame budget. Incremental per-entity GPU updates are the next useful
  optimization if denser or highly animated regions exceed that budget.
- Synchronous readback costs roughly 31–36 ms per frame. A direct GUI path or
  asynchronous staging ring removes it from presentation.
- Five referenced textures were unavailable from the test grid. Missing assets
  do not deadlock readiness; completeness remains explicit.
- The successful live run used 280 visible prims, one avatar, 51 terrain
  patches, 427,253 render triangles, 4,096 event slots, and dropped zero events.
