AgentCanvas / Pages / Developer Guide / Nodesets / Env / Matterport3D
2026-06-11 13:46

The Matterport3D NodeSet (EnvMP3DNodeSet, node prefix env_mp3d) wraps MatterSim — the discrete panoramic navigation simulator behind R2R and its sibling VLN datasets — as a set of pure environment tools. This page is about how the environment is abstracted: the decoupled env/method split, the three-tier env contract that lets agent graphs port across simulators, and the per-view image primitive that replaced the old composite panorama.


1. The abstraction in one picture

MatterSim is a stateful C++ simulator: it holds one scene mesh + one agent pose, renders a camera frame per makeAction, and exposes a connectivity graph of viewpoints per scene. AgentCanvas hides all of that behind a NodeSet whose job is deliberately narrow:

MP3DEnvManager singleton

Like every server-mode env, all simulator state lives in a process-wide singleton with a dedicated single-threaded executor (GL/sim thread affinity). Canvas nodes never touch MatterSim directly — they marshal blocking calls onto the executor:

mgr = MP3DEnvManager.get()           # always the same instance
pano = await loop.run_in_executor(mgr.executor, mgr.graph_render_panorama, n_headings)
obs  = await loop.run_in_executor(mgr.executor, mgr.graph_get_observation)

Because the simulator is stateful (per-worker scene + agent pose), the nodeset declares parallelism = "replicated": each eval worker gets its own subprocess + sim instance rather than sharing one.


2. The three-tier env contract

Every env NodeSet (Habitat, MP3D, future AI2-THOR) follows the same tiered shape so that agent graphs wired only to the required ports are env-portable. env_mp3d populates all three tiers:

TierNodesWhat it guarantees
1 — Gym facade (required) env_mp3d__reset, env_mp3d__step The canonical two-node interface. reset fires once at run start (no required input) and bundles the full initial observation; step(action) teleports to a viewpoint and emits the same-shaped bundle + done. Both carry the locked contract ports — instruction TEXT, episode_id TEXT, observation LIST[IMAGE], pose POSE — so a graph wired only to these is portable.
2 — Env-specific extras (same two nodes) Alongside the contract ports, MP3D adds scan_id, viewpoint_id, heading, navigable_json, dataset, extras_json, and the per-view primitive (views / view_meta / depth_views). Discrete VLN methods read these; CE-portable graphs ignore them.
3 — Graph-tier primitives episode_info, navigate_to, observation, graph_panorama, evaluate The decomposed building blocks the gym facade bundles internally. Wire these by hand when reset/step don't fit — e.g. re-using one render across two perception nodes, or a custom panorama cadence.

The gym facade is sugar: reset = episode_info + graph_panorama + observation, and step = navigate_to + graph_panorama + observation, fused into one node so the downstream agent sees an identical observation shape at step 0 and every step after.


3. The per-view image primitive

This is the load-bearing piece of the abstraction and the one most recently reshaped. MatterSim renders a panorama as N headings × 3 elevations discrete views (default 12 × 3 = 36, reproducing the NavGPT sweep with elevations −30°/0°/+30°). The env now emits those views as a list, never as a stitched grid image:

PortTypeMeaning
viewsLIST[IMAGE]Per-view RGB, one image per rendered view, in render order.
view_metaTEXT (JSON)Aligned 1:1 with views: [{view_index, heading_deg, elevation_deg, direction}, …]. view_index is the native MatterSim 0–35 index.
depth_viewsLIST[DEPTH]Per-view depth in metres, aligned with views; empty when depth is disabled.

The contract-required observation port is just views under its portable name. A built-in imageViewer renders LIST[IMAGE]/LIST[DEPTH] as a tiled strip, so the panorama is still inspectable on the canvas without the env building a composite.

Why per-view instead of a composite panorama

The env used to stitch the 36 views into a single horizontal grid image (composite / depth_composite) plus a directions string. That pushed image-handling logic back into every method: MapGPT, the four NavGPT perception nodes, and DiscussNav each hard-coded the grid geometry (border, label height, tile layout) to crop the panorama apart again. The round-trip was lossy and contract-violating:

Inverting the contract — env hands out primitives, methods index into them — deleted all three crop paths and let each candidate carry an exact view_index (see next section). This is the same env-emits-primitives principle from §1, applied to pixels.

Candidate → view_index mapping

On reset/step, every entry in navigable_json gains a view_index field: the rendered view whose (heading, elevation) is nearest that candidate's direction. With 36 views the match lands on the correct elevation row, so a method can pull the exact image for a candidate by index — no cropping, no heading guesswork. Graph-granularity methods that don't need per-candidate imagery (NavGPT-MP3D, SpatialNav) simply ignore the field.


Interface migrated (2026-06-09). This nodeset now implements the gym-like env interfacereset (metadata only), step_* (control signals, no observation), observe_* (pull perception), evaluate. The node list below predates the migration and may be stale; the template's migration map (§6) and capability matrix (§3) are authoritative.

4. Canvas nodes

get_tools() returns seven nodes — two gym-facade, five graph-tier primitives:

Node TypeDisplayKey inputsKey outputsRole
env_mp3d__resetMP3D: Resettrigger (optional)instruction, episode_id, observation, pose, navigable_json, views, view_meta, depth_views, …Tier 1 — one-shot initial bundle.
env_mp3d__stepMP3D: Stepaction (viewpoint ID or STOP)same bundle as reset + doneTier 1 — teleport + post-step bundle.
env_mp3d__episode_infoMP3D: Episode Infotrigger (optional)instruction, episode_id, scan_idTier 3 — entry node for the instruction.
env_mp3d__navigate_toMP3D: Navigate Toviewpoint_idnew_viewpoint, turned_angle, success, error, (done on STOP)Tier 3 — graph teleport, writes trajectory.
env_mp3d__observationMP3D: Observationtrigger (optional)viewpoint_id, scan_id, heading, navigable_json, position_jsonTier 3 — raw structured state (no pixels, no prose).
env_mp3d__graph_panoramaMP3D: Graph Panoramatrigger (optional)views, view_meta, depth_views, scan_id, viewpoint_idTier 3 — render the per-view primitive at the current viewpoint.
env_mp3d__evaluateMP3D: Evaluatetrigger (optional)metrics (JSON), success, splTier 3 — R2R metrics from the accumulated trajectory.

Render granularity is a node config (n_headings: 8 → 24 views, or 12 → 36 views, default 12) on reset, step, and graph_panorama.

navigate_to is pure graph movement. An adjacent target is a single step; a non-adjacent target walks the shortest path and records every intermediate viewpoint. The full trajectory is written to graph_state under trajectory — that is what evaluate reads to compute SR / SPL / nDTW / SDTW / CLS / nav_error / oracle_success against the ground-truth path. One quirk worth knowing: the done output key is emitted only on STOP, to keep the executor's done-scan from terminating the episode mid-walk.


5. Env panel & datasets

MP3DEnvPanel is the canvas panel for episode selection. It exposes a three-field cascade — dataset → split → episode_index — plus Play / Pause / Stop / Reset actions. Changing dataset resets split + episode; changing split resets episode; any change fires an episode_reset signal so lifetime="episode" state containers downstream clear themselves.

DatasetSchema note
R2RRoom-to-Room — base schema (path + heading + single instruction).
R4RConcatenated R2R paths (longer, less direct).
RxRMultilingual; splits add a language suffix (val_unseen_en / _hi / _te). Language surfaces in extras_json.
REVERIEObject-grounded remote referring; target object id in extras_json.
CVDN / NDHDialog-history navigation; dialog turns in extras_json.

All six are surfaced through the one nodeset — the env panel cascade picks the schema, and dataset-specific fields ride along in extras_json so the contract ports stay uniform.


6. Server mode

MatterSim has its own native dependency stack, so the nodeset runs as an auto-hosted subprocess under a dedicated interpreter (MP3D_PYTHON, default ~/miniforge3/envs/ac-mp3d/bin/python). When loaded via POST /api/components/nodesets/env_mp3d/load?mode=server, the framework spins up the subprocess and proxies the nodes, preserving their node_type IDs (e.g. env_mp3d__step) so graphs run identically regardless of deployment mode.

Episode state lives inside the subprocess (the main-backend singleton is empty under the active-workspace overlay). Episode browsing from the canvas therefore goes through the env panel / /api/env-panels/env_mp3d/..., not through hidden tool nodes. Initialization kwargs (passed to initialize() at subprocess boot): width, height, vfov, preloading, depth, dataset, split, cache_size.


7. Typical graph workflow

A discrete LLM-VLN agent on MP3D wires the gym facade into a loop:

env_mp3d__reset ──┬─ instruction ───────────────┐
                  ├─ views / view_meta ──► (method: format candidates + images)
                  └─ navigable_json ────────────┘
                                                  │
                          (method LLM picks a viewpoint ID)
                                                  ▼
                              env_mp3d__step(action) ──┬─ views / view_meta / navigable_json ─► loop
                                                       └─ done ─► env_mp3d__evaluate ─► metrics

For the live status of each MP3D graph (verified vs. needs-investigation), see VLN Support Status §2.2. For the env-contract rationale across all environments, see NodeSets design doc.

AgentCanvas docs