PRIZM 4.0DSTA
Viewing C3 design language
RC3 · Components

Perception view

The operator's live 3D window into what a robot swarm perceives — the mapped structure, the areas of interest within it, and how fresh each part of the picture is. RC3 frames the surface and ships a reference renderer; an upstream system does the fusion.

Live · replay

A small sample scene replaying in-browser — a real renderer drawing real geometry, not invented telemetry. A swarm of platforms feeds one consolidated map of a mid-rise building: its window grid is structure (a mesh), with point clouds and a voxel floor plan over it, and AOIs highlighting specific windows. Drag to orbit; toggle layers; select an area of interest. Watch platforms drop out on a rolling cycle — their points fade and their source flips to `STALE` rather than holding a crisp last frame.

ENULIVE
Layers
Sources
UAV-09LIVE
UAV-11LIVE
UAV-14LIVE
UGV-04LIVE
UGV-05LIVE
UGV-07STALE 5s
Areas of interest

Bundled sample · replay

Feed posture

The `status` prop governs the whole view. `live` draws normally. When the consolidated feed stops, `holding` dims the scene and raises a banner with the age of the last good frame — a held picture can never read as live. `lost` replaces the scene with an unmistakable overlay. This is invariant 5 applied to space.

Live
ENULIVE

All sources fresh.

Holding
ENUHOLDING
Holding · last good 7s ago

Feed paused — last good frame, clearly held.

Feed lost
ENUFEED LOST
Feed lost

Link down — no scene, no ambiguity.

What RC3 owns — and what it doesn't

Perception view is the trustworthy screen, not the map-builder. RC3 does not fuse, register, or run SLAM — it assumes an upstream system consolidates the swarm's sensors into one clean scene, the same way the Controller interface frames a physical gamepad. RC3 owns the input contract, the operator chrome, and a reference renderer; a vendor engine can take over the drawing through `renderDelegate` while keeping both.

RC3 owns
  • The input data contract — ROS 2 / Foxglove-shaped layers.
  • Operator chrome — layer legend, AOI registry, per-source provenance, freshness.
  • A dependency-free reference renderer: points, voxels, mesh, markers, live transforms.
  • The AOI schema — the one piece RC3 defines natively.
Upstream / delegate owns
  • Sensor fusion, registration, SLAM — the clean scene arrives ready to display.
  • High-scale rendering — LOD, octree streaming, mesh reconstruction (via `renderDelegate`).
  • glTF / GLB / 3D Tiles binary decoding — a delegate concern.
  • The coordinate frame's truth — RC3 pins one and trusts the declared transforms.

Anatomy

A spatial canvas in the centre with chrome pinned to the edges. The geometry comes from the scene; the operator semantics come from the chrome.

Coordinate frame

The top-left chip names the canonical frame (ENU / NED / z-up / y-up). Sources declare their transform into it; mismatch is the number-one integration failure, so the frame is always on screen.

Layer legend

Per-layer toggles with a kind swatch and element count. Points, occupancy voxels, and structural mesh switch independently — the operator declutters without losing the others.

AOI registry

Areas of interest with kind tone (objective / hazard / inspect / marker) and confidence. Selecting one rings it in Ember in the canvas. The one schema RC3 defines natively.

Source provenance

Every contributing platform, its identity colour, and its freshness — LIVE or STALE with age. The picture is never anonymous; the operator can see who saw what, when.

Freshness decay

Geometry from a source that stops reporting fades and desaturates rather than vanishing or staying crisp. Stale data is visible as stale — invariant 5 applied to space.

Holding / lost

When the consolidated feed pauses or drops, the whole view raises an unmistakable overlay with the age of the last good frame. A frozen scene can never pass for live.

Reference renderer

A dependency-free canvas-2D projector: points, voxels, mesh wireframe, AOI markers, live per-source transforms. Lean by design — heavy rendering belongs to a delegate.

Render delegate

An escape hatch: pass `renderDelegate` to plug a vendor 3D engine into the centre. RC3 keeps drawing the chrome around it, so the contract and operator semantics are unchanged.

Props

PropTypeDefaultDescription
scenePerceptionSceneThe consolidated scene from upstream — frame, sources, layers, and AOIs. RC3 displays it; it does not fuse or register.
status"live" | "holding" | "lost""live"Overall feed posture. holding dims the scene and raises a last-good-frame banner; lost replaces it with an overlay. Honours invariant 5.
hiddenLayerIdsstring[]Controlled — layer ids the operator has toggled off. Pair with onToggleLayer.
onToggleLayer(layerId: string) => voidFired when a legend entry is tapped. The consumer owns the hidden set.
selectedAoiIdstring | nullnullControlled — the focused AOI. The selected marker is ringed in Ember on the canvas.
onSelectAoi(aoiId: string | null) => voidFired when an AOI is tapped. Passes null when the active AOI is tapped again.
staleAfterSecondsnumber3Source age beyond which geometry visibly decays and provenance flips to STALE. Tune to the feed's expected cadence.
autoRotatebooleanfalseSlowly orbit the reference camera. Respects prefers-reduced-motion and pauses while the operator drags. Ignored when a delegate renders.
renderDelegate(ctx: PerceptionRenderContext) => ReactNodeEscape hatch — render the geometry with a vendor 3D engine instead of the built-in renderer. RC3 still draws the chrome.
bareChromebooleanfalseDrop the layer legend, AOI registry, and provenance panels — e.g. for a compact tile.
classNamestringForwarded to the root container.

Types

PerceptionScene

The whole consolidated scene. The renderer auto-fits the camera to its bounds.

ts
interface PerceptionScene {
  frame: CoordinateFrame;          // "ENU" | "NED" | "z-up" | "y-up"
  sources: PerceptionSource[];
  layers: SceneLayer[];
  aois?: AreaOfInterest[];
}
SceneLayer

The three geometry layers, each tagged with an optional `sourceId` so freshness can decay it independently.

ts
type SceneLayer = PointCloudLayer | OccupancyLayer | MeshLayer;
// points    → sensor_msgs/PointCloud2 · Foxglove PointCloud
// occupancy → nav_msgs/OccupancyGrid · OctoMap · Foxglove Grid
// mesh      → pre-tessellated glTF / 3D Tiles backdrop
PerceptionSource

A contributing platform and how fresh its contribution is. `ageSeconds` drives decay and the holding state.

ts
interface PerceptionSource {
  id: string;                      // "UGV-04"
  label?: string;
  ageSeconds: number;              // since last fresh geometry
  transform?: FrameTransform;      // into the scene frame
}
AreaOfInterest

The operator-semantic schema RC3 defines natively. Layered over the geometry.

ts
interface AreaOfInterest {
  id: string;
  label: string;
  kind: "objective" | "hazard" | "inspect" | "marker";
  position: Vec3;
  sourceId?: string;
  confidence?: number;             // 0–1
  ageSeconds?: number;
}
FrameTransform

A source's pose in the canonical frame. The reference renderer honours translation + yaw; a delegate may use the full pose.

ts
interface FrameTransform {
  translation: Vec3;               // metres, into the scene frame
  yaw?: number;                    // radians about the up axis
}
PerceptionRenderContext

Handed to a `renderDelegate` so a vendor engine has everything the chrome knows.

ts
interface PerceptionRenderContext {
  scene: PerceptionScene;
  hiddenLayerIds: Set<string>;
  selectedAoiId: string | null;
  staleAfterSeconds: number;
  status: PerceptionStatus;
}

Wiring

Subscribe to the upstream scene, hold the hidden-layer set and the selected AOI, and feed each source's age from its last update. When a source stops, let its age climb rather than freezing the picture; when the whole feed stops, raise `status`.

tsx
// Live scene from an upstream perception subscription
<PerceptionView
  scene={{
    frame: "ENU",
    sources: feed.platforms.map((p) => ({
      id: p.id,
      ageSeconds: secondsSince(p.lastFrameAt),
      transform: { translation: p.position, yaw: p.yaw },
    })),
    layers: feed.layers,   // points / occupancy / mesh
    aois: feed.areasOfInterest,
  }}
  status={feed.connected ? "live" : "holding"}
  hiddenLayerIds={hidden}
  onToggleLayer={toggleLayer}
  selectedAoiId={selectedAoi}
  onSelectAoi={setSelectedAoi}
/>
tsx
// Hand the drawing to a vendor 3D engine, keep RC3's chrome
<PerceptionView
  scene={scene}
  renderDelegate={({ scene, hiddenLayerIds, selectedAoiId }) => (
    <YourEngine
      scene={scene}
      hidden={hiddenLayerIds}
      focus={selectedAoiId}
    />
  )}
/>

Behavioural rule

Invariant Five

Telemetry never silently stale

Perception is telemetry with a shape. Geometry from a source that stops reporting fades rather than staying crisp; per-source freshness is always on screen; and a held or lost feed raises an unmistakable overlay. A frozen scene can never pass for a live one.

Read invariant

Accessibility

Figure roleThe view carries `role="figure"` labelled by the coordinate-frame chip, so assistive tech announces a named spatial figure rather than a bare canvas.
Operable chromeLayer toggles and AOI entries are real buttons with `aria-pressed` state — reachable, focusable, and operable without the canvas. The 3D canvas is a visual amplifier, not the only path.
Freshness not colour-onlyStale sources and AOIs carry literal `STALE {age}` / age text alongside the dimmed treatment. Degradation is legible without relying on the danger colour.
Reduced motion`autoRotate` checks `prefers-reduced-motion` and does not animate the camera when the operator has asked the system to reduce motion.
Held state is explicitHolding and lost states render text — `HOLDING · last good Ns ago`, `FEED LOST` — not just a colour wash, so the posture is unambiguous to every operator.

JavaFX

Ships in the PRIZM JavaFX library for thick-client C3 apps as Rc3PerceptionView (extends StackPane). Run the gallery to see it natively.

java
import design.prizm.fx.rc3.Rc3PerceptionView;

Rc3PerceptionView()
MemberTypeDefaultDescription
CoordinateFrameenumENU / NED / Z_UP / Y_UP — the scene frame, shown in the top chip.
Vec3 / FrameTransform / PerceptionSourcerecordScene-frame point, rigid transform, and a contributing platform with freshness.
SceneLayersealed interfacePointCloudLayer / OccupancyLayer / MeshLayer — the geometry the renderer draws.
AreaOfInterest / AoiKindrecord / enumOperator semantics over the geometry (objective / hazard / inspect / marker).
PerceptionScenerecord(frame, sources, layers, aois)The consolidated scene from upstream.
PerceptionStatusenumLIVE / HOLDING / LOST — a held or lost feed raises an unmistakable overlay.
setScene(PerceptionScene) → voidThe scene to render.
setStatus / setStaleAfterSeconds / setAutoRotate / setBareChrome→ voidFeed posture, decay threshold, orbit toggle, and hide-panels mode.
setSelectedAoiId / setOnSelectAoi / setOnToggleLayer→ voidSelection + layer-toggle state and optional listeners (interactive by default).
RenderDelegate@FunctionalInterfaceDrop in a vendor 3D engine instead of the reference renderer; RC3 keeps drawing the chrome.

A JavaFX Canvas reference renderer projects the scene with an orbit camera (drag / scroll-zoom / auto-rotate); chrome mirrors the web (frame chip, layer legend, source provenance, AOI registry, holding / lost overlay). Honours invariant 5 — stale geometry decays and a frozen scene never reads as live. JavaFX divergence: the renderer's neutral colours are dark-tuned constants (the web reads live CSS vars). Mirrors components/rc3/perception-view.tsx.

Usage

Reach for the Perception view when the operator needs to see — and command through — what the swarm perceives in space. Feed it a consolidated scene from upstream; do not expect RC3 to fuse or register. Keep each source's `ageSeconds` honest so decay and the holding state work; raise `status` to `holding` or `lost` when the feed itself degrades. Start with the reference renderer; reach for `renderDelegate` only when scale demands a dedicated engine — and keep the chrome either way.