ObjectNav NodeSet
The ObjectNav NodeSet (EnvObjnavNodeSet) wraps habitat-lab 0.2.4 for object-goal navigation: an episode names a category and the agent must reach any instance of it and call STOP within 1 m. Two episode corpora are selectable from the env panel β HM3D (6 categories: chair, bed, plant, toilet, tv_monitor, sofa) and MP3D (21 categories, the Habitat-2020-challenge generation). Unlike the other nav envs here, the goal arrives as a bare noun, not a route description, so the whole task is search under semantic priors.
Sibling of Habitat, not an extension. Both wrap habitat, but they cannot share a process or an interpreter: Habitat pins habitat-sim 0.1.7 + Python 3.8 for VLN-CE, while ObjectNav pins habitat-sim 0.2.4 + habitat-lab 0.2.420230405 + Python 3.9 in its own ac-objnav env. Different simulator generation, different config schema (habitat.* omegaconf vs VLN-CE's TASK_CONFIG.* yacs).
1. Overview
Benchmark configuration is the paper standard, not the challenge
ObjectNav has two live configurations, and they are not comparable. The Habitat Challenge 2023 pins challenge-2023 habitat-lab and a Stretch robot embodiment; the published literature β VLFM (23.12), SG-Nav, and the RSS-2026 pair MVP-Nav / OpenFrontier β instead uses habitat 0.2.4 with the classic cylindrical agent inherited from Challenge 2022. This nodeset implements the latter, because that is what every number in every comparison table was produced under:
| Knob | Value | Source |
|---|---|---|
| habitat-sim / habitat-lab | 0.2.4 / 0.2.420230405 | VLFM pyproject.toml; OpenFrontier states "Habitat 0.2.4" |
| Forward step | 0.25 m | Challenge-2022 agent |
| Turn angle | 30Β° | Challenge-2022 agent |
| Sensors | 640Γ480 RGB-D + GPS + compass | Challenge-2022 agent |
| Step budget | 500 | Challenge-2022 agent |
| Success radius | 1.0 m from any goal instance | ObjectNav Revisited (20.06) |
| Episodes (HM3D) | objectnav_hm3d_v1 val β 2000 episodes, 20 scenes, 6 categories | VLFM, MVP-Nav (v2 also selectable β OpenFrontier's choice) |
| Episodes (MP3D) | objectnav_mp3d_v1 val β 2195 episodes, 11 scenes, 21 categories | Habitat 2020 challenge generation, the ObjectNav Revisited standard; VLFM's MP3D table uses exactly this dataset |
One nodeset serves both corpora because habitat-lab 0.2.4's objectnav_mp3d.yaml and objectnav_hm3d.yaml benchmark configs are identical except for data_path (verified by a resolved-config field diff): same agent, sensors, action space, success criterion and budget. Selecting a dataset in the env panel is therefore an episode-corpus choice, not a task change β method graphs never see the difference.
Architecture
The three-layer shape shared by every env nodeset:
ObjnavEnvManager(singleton) β owns one non-vectorizedhabitat.Env, a lock, and a single-thread executor pinning all simulator work to one OS thread (GL affinity). Blocking methods only; the nodes hop onto its executor.- Canvas nodes β the four gym verbs, thin adapters over the manager.
EnvObjnavNodeSetβserver_python = conda_env_python("ac-objnav", "OBJNAV_PYTHON"), so the framework auto-hosts the nodeset in its own subprocess under?mode=server.
Server-mode only in practice: habitat_sim is not installed in the default agentcanvas env. Override the interpreter with $OBJNAV_PYTHON if your env lives elsewhere.
2. Canvas nodes
Seven nodes on the gym contract: reset carries metadata only, step_* returns control signals only, perception is pulled via observe_*, and evaluate is a thin sink for the after-loop band.
| Node type | Display name | Input ports | Output ports | Purpose |
|---|---|---|---|---|
env_objnav__reset |
ObjNav: Reset | trigger (ANY, optional) |
object_category, episode_id, scene_id |
Ensure a live episode β re-arm in place if done, read untouched if live. Never chooses an episode (that is env-panel-owned). No observation ports. |
env_objnav__step_discrete |
ObjNav: Step (discrete) | action (ACTION, 0β5) |
reward, terminated, truncated, info |
Advance one tick. reward is always 0.0 β this is an eval task, habitat computes no shaped reward here. |
env_objnav__step_pose |
ObjNav: Step (pose) | target (POSE) |
reward, terminated, truncated, info |
Walk toward a world-frame target via ShortestPathFollower with real discrete primitives β every step counts toward SPL and the budget. Stops at 0.36 m radius, 50 nav-steps, or episode end; never dispatches STOP (committing is the reasoning side's decision). The frontier-method workhorse: the method picks a point, the env walks there. |
env_objnav__observe_egocentric |
ObjNav: Observe (egocentric) | trigger (ANY, optional) |
rgb, depth, pose, intrinsics, gps, compass |
Idempotent read of the current frame; never advances the env, never auto-resets. gps/compass are the ObjectNav task sensors (position and heading relative to episode start) that VLFM-style methods integrate into a map. |
env_objnav__observe_camera_pose |
ObjNav: Camera Pose | trigger (ANY, optional) |
position, rotation |
World-frame position [x,y,z] + orientation quaternion [x,y,z,w] as bare lists β for projection nodes that want raw values rather than the POSE bundle. |
env_objnav__observe_panorama |
ObjNav: Observe (panorama) | trigger (ANY, optional) |
views, directions, n_views, composite |
N-heading panorama rendered off-pose via get_observations_at (pure read, never steps). Two representations on a config switch: views_rgbd β aligned RGB + depth (8-bit viewer PNG + 16-bit mm raw) per heading; composite β one stitched labelled grid image for single-image VLM prompts. 4β24 views, default 12 (30Β° bins). Expensive: one render per view, pull on demand. |
env_objnav__evaluate |
ObjNav: Evaluate | trigger (ANY, optional) |
metrics, success, spl |
Pull task metrics on demand (success, spl, soft_spl, distance_to_goal). Independent of done, so it also works mid-episode. |
Action space
habitat 0.2.4's ObjectNav defaults, in index order. The order matters: a textParse node configured with these six choices emits an index that is the action, so no mapper node is needed between reasoning and env.
| Index | Action | Effect |
|---|---|---|
| 0 | STOP | End the episode β the only way to score a success |
| 1 | MOVE_FORWARD | 0.25 m |
| 2 | TURN_LEFT | 30Β° |
| 3 | TURN_RIGHT | 30Β° |
| 4 | LOOK_UP | Tilt camera up 30Β° |
| 5 | LOOK_DOWN | Tilt camera down 30Β° |
terminated vs truncated
habitat exposes a single episode_over flag; the manager splits it into the two gym signals by looking at what caused it. An explicit STOP (action 0) is terminated β the agent committed, and the metrics reflect its judgment. Running out of the step budget without ever calling STOP is truncated. Terminal metrics ride info["metrics"] in both cases, so a graph that ends on budget still harvests a verdict.
3. Env panel
ObjnavEnvPanel owns episode placement β there are no split/episode canvas nodes. Its cascade is three deep:
- dataset β
hm3d_v1(paper standard),hm3d_v2(Challenge-2023 episodes, OpenFrontier's choice) ormp3d_v1(2020-challenge generation, 21 categories). Changing it rebuilds the env and re-scopes the split list. Legacy valuesv1/v2are accepted everywhere as aliases for the HM3D pair. - split β
val(2000 episodes on HM3D, 2195 on MP3D) orval_mini(30, for smokes).trainis deliberately absent: the HM3D train scenes are not staged, MP3D's train episodes would be eagerly loaded just to fill a dropdown, and zero-shot methods do not train. - episode_index β labelled
{index}: {scene} β {category}so the goal is visible when picking.
Field changes emit the episode_reset signal, clearing every lifetime="episode" state container. play re-seats the selected episode before starting the run.
4. Data staging
Scenes come from the official Matterport distribution (login-gated; an API token works headlessly via curl --user token-id:secret). Episodes are public direct downloads from dl.fbaipublicfiles.com β with one trap: the MP3D episode zip's official URL spells the corpus m3d (β¦/objectnav/m3d/v1/objectnav_mp3d_v1.zip); the expected mp3d path returns 403.
| Path | Contents |
|---|---|
data/scene_datasets/hm3d/val/ | 100 HM3D v0.2 val scenes (.basis.glb + .basis.navmesh), plus .semantic.glb/.semantic.txt for the 36 annotated ones |
data/datasets/objectnav/hm3d/v1/ | objectnav_hm3d_v1 β val (2000 eps / 20 scenes), val_mini (30), train |
data/datasets/objectnav/hm3d/v2/ | objectnav_hm3d_v2 β same split shape |
data/scene_datasets/mp3d | compat symlink β ../habitat/scene_datasets/mp3d: all 90 habitat-format MP3D scans (.glb + .house + .navmesh + _semantic.ply), long staged for VLN-CE; episodes bake mp3d/{scan}/{scan}.glb |
data/datasets/objectnav/mp3d/v1/ | objectnav_mp3d_v1 β val (2195 eps / 11 scenes / 21 categories; sharded per-scene under content/, the outer val.json.gz is an empty shell), val_mini (30), train |
scene_id path, and the two generations use different prefixes: v1 says hm3d/val/β¦, v2 says hm3d_v0.2/val/β¦, and v2's val_mini points at hm3d_v0.2/minival/β¦ (its two scenes, 00800 and 00802, are a subset of the val download). Both v2 prefixes are served by compat symlinks into the single real scene tree β data/scene_datasets/hm3d_v0.2/val β ../hm3d/val and β¦/minival β ../hm3d/val. Without them habitat-sim aborts with "Missing (at least) one of scene dataset attributes β¦ Likely an invalid scene name".Semantic meshes are not required for scoring
Success is judged against goal viewpoints embedded in the episode JSON, not against a runtime semantic lookup β verified by driving ShortestPathFollower to the nearest viewpoint and getting SR 1.00 with no semantic files present. The .semantic.glb files are staged anyway because habitat's top_down_map measure reads the semantic scene, and reference implementations (VLFM among them) enable it.
5. Probe graph: objnav_hm3d
workspace/graphs/vln/unverified/objnav_hm3d.json β the ObjectNav analog of simple_navigator, and the graph that exercises this nodeset end to end. Every reasoning node ships with the platform; there is no method-specific code anywhere in it.
llmCall,textParse,historyLogβ built-in gluebasic_agent__image_analyze,basic_agent__obs_to_textβ observation encoding- the four verbs above β env side
The loop is: observe β caption the frame β format an observation string β LLM picks one of the six actions β parse β step. The parse node's choice list is written in action-index order (STOP,FORWARD,LEFT,RIGHT,LOOK_UP,LOOK_DOWN), so its index output wires straight into step_discrete.action. step_discrete.terminated drives iter_out.stop; evaluate hangs off iter_out.final_stop in the after-loop band, which is what keeps the terminal STOP step inside the harvested metrics. The graph is also corpus-agnostic β nothing in it names a dataset, so the same JSON runs MP3D by submitting with dataset=mp3d_v1.
6. Verification evidence
HM3D rows 2026-07-20 (slot-b backend); MP3D rows 2026-07-21 (slot-c backend). Single RTX 3090, ac-objnav env.
| Check | Evidence | Result |
|---|---|---|
| Bare-sim render (pre-nodeset) | habitat-sim opens 00800-TEEsavR23oF headless | navmesh loaded, RGB 480Γ640 (99% non-zero), depth 0β4.9 m |
| Dataset shape | objectnav_hm3d_v1 val via habitat-lab | exactly 2000 episodes / 20 scenes / 6 categories, goals carry viewpoints |
| Scoring sanity | oracle ShortestPathFollower vs random agent, 5+3 episodes | oracle SR 1.00 (SPL 0.90β0.98, stop 0.02β0.08 m from goal); random SR 0.00 β metric is not vacuous |
| Manager surface | init / observe / step / STOP / ensure_live / set_episode / episode list | all pass; STOP β terminated=True, truncated=False with metrics attached |
| Budget exhaustion | max_steps=5, forward-only, never STOP | terminated=False, truncated=True, terminal metrics still attached |
| LOOK_UP / LOOK_DOWN | actions 4 and 5 dispatched directly | accepted, step counter advances |
| v2 dataset switch | switch_dataset_split("v2", "val_mini") | 30 episodes load (after the symlink fix in Β§4) |
| Consecutive-episode sequencing | 4 placements walking a scene change and a same-scene pair, stepping away between each | every episode starts at its own start_position (drift 0.0000 m) with fresh counters; a revisited episode reproduces its start pose exactly β the 0.1.7 stale-pose bug does not reproduce on 0.2.4 |
| Server mode | load?mode=server on the slot backend | auto_host subprocess up, 4 tools registered, env panel bridged via RemoteEnvPanelProxy, split change rebuilds val β val_mini |
| Graph wiring | POST /api/graphs/validate | 24/24 edges checked, zero wire errors |
| Graph run | run 20260720_173306 (/experiment:run objnav-hm3d objnav_hm3d, 1 ep, budget 8) | completed β resetΓ1 / observeΓ9 / stepΓ8 / evaluateΓ1; metrics harvested into aggregate |
| Multi-worker batch | run 20260720_205631 β val_mini all 30 episodes, worker_count=4, budget 8 | 30/30 completed in 931 s (~4Γ serial); balanced 8/8/7/7 across workers; 30 unique (scene, episode_id) pairs with 30 distinct distance_to_goal values and no same-scene identical-metric pairs β no cross-worker state bleed, no stale-pose signature. Measured VRAM ~535 MB/worker (peak 2897 MB incl. 755 MB baseline); host RAM ~3.4 GB/worker |
| MP3D config parity | resolved objectnav_mp3d.yaml vs objectnav_hm3d.yaml field diff | identical except data_path β same agent, sensors, actions, success criterion, budget |
| MP3D dataset shape | objectnav_mp3d_v1 val via habitat-lab | exactly 2195 episodes / 11 scenes / 21 categories; val_mini 30; sharded content/ load works |
| MP3D oracle install check | metric-blind ShortestPathFollower to episode viewpoints, STOP on planner termination | SR 30/30 val_mini + 33/33 (3 eps Γ all 11 val scenes), mean SPL 0.92β0.93 |
| MP3D server mode + dataset cascade | load?mode=server on the slot backend, panel field push dataset=mp3d_v1 then split=val_mini | 7 tools registered; rebuild β 2195 then 30 episodes; options labelled; reset action re-seats and emits episode_reset |
| HM3D alias regression | switch_dataset_split("v1", "val_mini") | legacy value resolves to hm3d_v1, 30 episodes load |
The evaluate count is the load-bearing one. It confirms the after-loop band fires and this graph does not reproduce the harvest miss that affects several gym-migrated graphs (roadmap TODO #64), where a loop-body evaluate never sees the terminal STEP and records success=0.
A sizing trap worth inheriting
The first probe run died at step 10 of 30 with "timeout after 150.0s". ADR-eval-002 caps each episode at step_budget Γ default_per_step_budget_sec, and the nodeset originally declared 5.0 β correct for a bare habitat step, wildly wrong for a graph making two vision-LLM calls per step (~15 s). It is now 30.0, matching Habitat's value and for the same reason. Any env destined for LLM-in-the-loop graphs needs the larger number, and the symptom of getting it wrong looks like an env bug rather than a budget one.
Oracle stop precision on MP3D
MP3D's 2020-era goal viewpoints are sparser than HM3D's, which breaks naive oracle harnesses twice. A follower goal_radius of 0.25 m stops outside the 0.1 m success criterion β the first attempt scored SR 0.567 with every failure sitting at distance_to_goal 0.11β0.25. Tightening the radius to 0.1 makes the follower oscillate around it on 0.25 m steps until the budget burns, standing at dtg β 0.04 with no STOP ever dispatched. The working form keeps the stop decision metric-blind: goal_radius = success_distance plus a no-progress rule (net displacement under 0.05 m across a 50-step nav call means the discretization is at its limit β commit STOP where standing). HM3D's dense viewpoints mask both traps: the same loose harness scores 1.0 there.
7. Not yet verified
Stated plainly so nobody reads the table above as broader than it is:
- Long episodes. Every batch run so far capped episodes at 8 steps for API cost; the full 500-step budget has only been exercised by the manager-level oracle/budget smokes.
- Canvas Play. Verified through the eval-batch path only; the viewer nodes (
imageViewer,textScroll,actionLog,metrics) executed but were never watched in the GUI. - The STOP path through the graph. The probe loop ended on step budget, so the
terminated β iter_out.stopwire has never been driven truthy in a real run. The env-side STOP itself is verified at manager level. - Any success rate. No method has run on this env β only a nano-model toy agent. No SR number from this stack should be quoted yet.
- MP3D multi-worker. The 4-worker VRAM/RAM figures above were measured on HM3D; MP3D's building-scale meshes are larger. Re-measure on the first real MP3D batch (the
objnav-mp3dprofile note says the same).