From 61b2fbee9682ddeb7b0bc09f64c018509d18a01b Mon Sep 17 00:00:00 2001 From: yuecideng Date: Sat, 10 Oct 2026 14:37:26 +0000 Subject: [PATCH 1/4] feat(scene): add fixed scene candidates for Task Program demos --- agent_context/MAP.yaml | 16 + .../topics/motion-planning/motion-planning.md | 4 + .../topics/robot-workspace/robot-workspace.md | 4 + .../topics/scene-expansion/scene-expansion.md | 81 +++ .../topics/task-programs/task-programs.md | 1 + docs/architecture/README.md | 1 + .../task-program-scene-expansion-design.md | 261 +++++++ .../embodichain/embodichain.lab.sim.rst | 9 + .../embodichain.lab.sim.scene_expansion.rst | 119 ++++ ...odichain.lab.task_program.integrations.rst | 5 + ...k_program.integrations.scene_expansion.rst | 119 ++++ embodichain/lab/gym/envs/demo.py | 46 +- embodichain/lab/gym/envs/embodied_env.py | 16 + .../lab/sim/scene_expansion/__init__.py | 34 + embodichain/lab/sim/scene_expansion/cfg.py | 63 ++ .../lab/sim/scene_expansion/contracts.py | 189 ++++++ embodichain/lab/sim/scene_expansion/source.py | 107 +++ .../integrations/scene_expansion.py | 314 +++++++++ .../envs/task_program/test_scene_expansion.py | 639 ++++++++++++++++++ tests/sim/scene_expansion/test_contracts.py | 145 ++++ tests/sim/scene_expansion/test_source.py | 153 +++++ tests/test_agent_context_map.py | 1 + 22 files changed, 2311 insertions(+), 16 deletions(-) create mode 100644 agent_context/topics/scene-expansion/scene-expansion.md create mode 100644 docs/architecture/task-program-scene-expansion-design.md create mode 100644 docs/source/api_reference/embodichain/embodichain.lab.sim.scene_expansion.rst create mode 100644 docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.scene_expansion.rst create mode 100644 embodichain/lab/sim/scene_expansion/__init__.py create mode 100644 embodichain/lab/sim/scene_expansion/cfg.py create mode 100644 embodichain/lab/sim/scene_expansion/contracts.py create mode 100644 embodichain/lab/sim/scene_expansion/source.py create mode 100644 embodichain/lab/task_program/integrations/scene_expansion.py create mode 100644 tests/gym/envs/task_program/test_scene_expansion.py create mode 100644 tests/sim/scene_expansion/test_contracts.py create mode 100644 tests/sim/scene_expansion/test_source.py diff --git a/agent_context/MAP.yaml b/agent_context/MAP.yaml index c09b5d5b1..5afc26ab6 100644 --- a/agent_context/MAP.yaml +++ b/agent_context/MAP.yaml @@ -201,6 +201,22 @@ topics: tests/sim/motion/planners/, tests/compute/] related_topics: [simulation-system, robot-system, ik-solvers, atomic-actions] status: active +- id: scene-expansion + title: Scene Expansion + aliases: [scene expansion, scene augmentation, scene variant, 场景扩增, 场景变体] + keywords: [SceneExpansionCfg, ScenePoseChange, SceneVariant, FixedSceneCandidateSource, + ScenePreparationResult, SceneVariantEpisodeResult, execute_scene_variant] + paths: [topics/scene-expansion/scene-expansion.md] + source_of_truth: + - embodichain/lab/sim/scene_expansion/ + - embodichain/lab/task_program/integrations/scene_expansion.py + - embodichain/lab/gym/envs/embodied_env.py + - embodichain/lab/gym/envs/demo.py + watch_paths: [embodichain/lab/sim/scene_expansion/, + embodichain/lab/task_program/integrations/scene_expansion.py, + tests/sim/scene_expansion/, tests/gym/envs/task_program/test_scene_expansion.py] + related_topics: [simulation-system, task-programs, robot-workspace, motion-planning, gen-sim, data-pipeline] + status: active - id: rl-learning title: RL Learning Pipeline aliases: [rl learning, rl config, reinforcement learning, 强化学习, 训练配置] diff --git a/agent_context/topics/motion-planning/motion-planning.md b/agent_context/topics/motion-planning/motion-planning.md index 4a4efde0a..9150d5651 100644 --- a/agent_context/topics/motion-planning/motion-planning.md +++ b/agent_context/topics/motion-planning/motion-planning.md @@ -77,6 +77,10 @@ encode prepared actions rather than retiming a second time. ## Trajectory augmentation boundary +Scene layout proposals and their Task Program execution adapter belong to +[scene expansion](../scene-expansion/scene-expansion.md). The trajectory +contracts below operate within a specified scene and initial state. + `motion/expansion/` owns callable-free Expansion Profiles, immutable templates and candidates, sampled limit checks and coverage. `load_expansion_profile()` strictly decodes the task-local resource, separate from environment diff --git a/agent_context/topics/robot-workspace/robot-workspace.md b/agent_context/topics/robot-workspace/robot-workspace.md index 1b4d999d9..946404ce6 100644 --- a/agent_context/topics/robot-workspace/robot-workspace.md +++ b/agent_context/topics/robot-workspace/robot-workspace.md @@ -4,6 +4,10 @@ Workspace analysis caches kinematic reachability; runtime sampling reuses cached joint configurations with the current robot base. This does not establish collision-free motion, grasp validity or task success. +[Scene expansion](../scene-expansion/scene-expansion.md) owns scene proposals +and the Task Program attempt boundary. Workspace results alone do not supply +physical preparation or acceptance of those proposals. + ## Entry points Paths below are relative to `embodichain/lab/sim/` unless qualified. diff --git a/agent_context/topics/scene-expansion/scene-expansion.md b/agent_context/topics/scene-expansion/scene-expansion.md new file mode 100644 index 000000000..c3142c2c8 --- /dev/null +++ b/agent_context/topics/scene-expansion/scene-expansion.md @@ -0,0 +1,81 @@ +# Scene expansion + +Scene expansion proposes changes before a new Task Program execution. The core +is independent of Task Program, GenSim and Gym; injected host callbacks own the +actual scene preparation and measured task evidence. + +## Entry points + +| Request | Owner | +|---|---| +| Shared proposal contracts and fixed source | `embodichain/lab/sim/scene_expansion/` | +| Candidate limits and movable entity permissions | `scene_expansion/cfg.py` in that package | +| Arena poses and proposal identity | `scene_expansion/contracts.py` in that package | +| One Task Program attempt and result metadata | `embodichain/lab/task_program/integrations/scene_expansion.py` | +| Reset-time persistence eligibility | `embodichain/lab/gym/envs/embodied_env.py` | +| Action consumption and recording lifecycle | `embodichain/lab/gym/envs/demo.py` | + +## Resolution and ownership + +```text +SceneExpansionCfg + authored pose changes + -> FixedSceneCandidateSource -> SceneVariant + -> injected prepare_scene -> ScenePreparationResult + -> canonical Task Program bridge -> execute_demo_episode -> env.step + -> measured_validator -> SceneVariantEpisodeResult + -> caller-owned reset commit/discard +``` + +`FixedSceneCandidateSource` snapshots only its budgeted prefix and owns no +execution lifecycle. Core contracts describe changes to existing physical +entities, not a replacement SceneGraph or runtime registry. Current execution +is B=1 and requires a Task Program; explicit canonical bridge creation prevents +legacy segment overrides from changing the selected source. + +The preparation callback is responsible for restore/application, physical +settling, actual initial-state checks, saved-state identity, binding refresh and +recording observation refresh. Generic execution does not provide those +physical operations. The measured callback supplies task-specific state and +event evidence. Gym remains the owner of reset, stepping and recording; no new +generation session or persistence writer is introduced. + +## Invariants + +- A changed pose is a proper 4x4 entity-to-local-arena Z-up transform, excluding + replication offsets. Numeric inputs become immutable tuples. +- Proposal identity includes parent scene, seed, ordinal, geometry and + provenance. It is independent of physical slots and distinct from actual + settled initial-state and execution-attempt identities. +- Only declared movable entity UIDs may change. Candidate and metadata + containers are owned; duplicate entity IDs and invalid configuration fail + explicitly rather than becoming ordinary geometric rejections. +- Initial checks, program completion and measured acceptance remain separate. + Empty or unavailable required checks cannot accept data; projected effects + do not replace physical evidence. +- Failed/unverified scene metadata blocks reset-time dataset, camera and + trajectory saves, including explicit commit rows. Reset consumes that + metadata. Acceptance is not a receipt proving persistence. + +Use [robot workspace](../robot-workspace/robot-workspace.md) for reachability +cache and frame contracts, [motion planning](../motion-planning/motion-planning.md) +for trajectories/collision worlds, and [Task Programs](../task-programs/task-programs.md) +for compilation, live grounding and runtime evidence. Those capabilities do +not by themselves provide scene restoration or certify a new layout. + +## Change sites and validation + +Keep layout/proposal algorithms under `lab/sim/scene_expansion`; put +task-specific adaptation under Task Program integrations. Keep GenSim scene +materialization in its own backend instead of introducing imports into the +core. Extend the host boundary when adding physical preparation, and +preserve Gym lifecycle ownership. + +```bash +pytest -q tests/sim/scene_expansion +pytest -q tests/gym/envs/task_program/test_scene_expansion.py +python docs/scripts/check_api_docs.py +``` + +The focused tests exercise immutable geometry and identities, bounded source +consumption, real demo dispatch and reset-time recording gates using CPU ports. +They do not establish physical success across robot models or scene layouts. diff --git a/agent_context/topics/task-programs/task-programs.md b/agent_context/topics/task-programs/task-programs.md index 89422dd7e..e673dc0bf 100644 --- a/agent_context/topics/task-programs/task-programs.md +++ b/agent_context/topics/task-programs/task-programs.md @@ -91,6 +91,7 @@ not by Task Program or Gym. - [Language and semantic integration](configuration.md): strict decoding, scene/profile contracts, component ownership and service allowlists. - [Assurance and execution](execution.md): measured/projected effects, acceptance, parallel calls and MLLM boundaries. - [Dataset persistence](../data-pipeline/data-pipeline.md): recorder commits, fragments, failures and finalization. +- [Scene expansion](../scene-expansion/scene-expansion.md): fixed scene proposals, injected preparation, independent measured acceptance and reset-time persistence gates. ## Reference integrations diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 487fc5df7..8c95616d0 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -12,6 +12,7 @@ Local and CI Sphinx builds regenerate their own version-specific assets. - [Original Task Program sample](task-program.sample.json): unchanged historical contract example. - [Earlier frontend snapshot](preview.snapshot.json): retained as a reference, no longer consumed by the app. - [GenSim / TaskSpec design](gen-sim-taskspec-design.md): current implementation status, ownership boundaries, and staged target architecture. +- [Task Program scene expansion design](task-program-scene-expansion-design.md): workspace integration, shared expert generation workflow, and three-developer implementation plan. `agent_context/MAP.yaml` remains the sole topic inventory. Groups are presentation choices. Neither import declarations nor adjacency establish runtime order. Every diff --git a/docs/architecture/task-program-scene-expansion-design.md b/docs/architecture/task-program-scene-expansion-design.md new file mode 100644 index 000000000..dc3c6132a --- /dev/null +++ b/docs/architecture/task-program-scene-expansion-design.md @@ -0,0 +1,261 @@ +# Task Program Scene Expansion and Expert Trajectory Generation Design + +Status: Design proposal, to be implemented in stages. Date: 2026-10-10 (Asia/Shanghai). + +Implementation and review tracking: [Issue #748](https://github.com/DexForce/EmbodiChain/issues/748). + +This design connects robot workspace analysis, scene expansion that preserves task semantics, and Task Program execution into a shared expert trajectory generation workflow for handwritten Task Programs and GenSim. It retains a dedicated `embodichain.lab.sim.scene_expansion` module for spatial candidate generation. Task Program integrations connect task constraints to execution, while Gym continues to own the physical lifecycle and recording. + +Code references: workspace baseline `57334705733d9d3208795f77e6df2442e896a001`; reviewed revision of [PR #729](https://github.com/DexForce/EmbodiChain/pull/729): `b60d90ee1f972ad12c8e81c715faf70e4ee8c6f7`. The PR was still OPEN when checked. The following sections distinguish existing foundations from proposed capabilities; they do not imply that this design is implemented or that the PR has merged. + +## 1 Goals and Scope + +The goal is to obtain expert trajectories with measured evidence of success by varying scene layouts, adding distractors, and selecting robot deployment poses while preserving task meaning. Expansion has two levels: first generate new scene instances, then optionally augment motion within a validated scene. + +Two entry points are supported: + +- Handwritten Task Programs: authors provide the program, environment configuration, role bindings, permitted scene variations, and acceptance criteria. +- GenSim: TaskSpec and Task Engine provide task semantics and program generation, while Scene Engine provides the base scene and materialization capabilities. + +Shared constraints: + +- Preserve task goals, role requirements, required ordering, and success criteria. Free-space motion paths and implementation details not constrained by the task may change. +- Distractors may block the original path; planning and execution validation must run again in the new scene. +- Handwritten environments keep the robot fixed by default. Robot deployment search is added in a later stage; GenSim may jointly search deployment poses and layouts. +- The initial version uses B=1, rigid-body Pick/Place tasks, a fixed robot, and a fixed pool of distractor assets. +- This feature does not integrate handwritten expert functions, fixed action/qpos sequences, or real2sim trajectories. Existing expert APIs and expansion capabilities for other sources remain compatible. +- The initial version does not vary robot models, object sizes, physical parameters, or the physics backend, and does not introduce mobile-base planning during execution. +- Scene expansion configuration remains separate from `program.yaml`; no executable DSL syntax is added. + +## 2 Existing Foundations and Proposed Additions + +| Existing capability | Current responsibility | Proposed addition | +| --- | --- | --- | +| `Robot.sample_reachable_pose()` and workspace caches | Sample cached qpos and perform FK using the current robot base | Use samples to screen object manipulation poses, layouts, and deployment candidates | +| Workspace spatial randomization functor | Set object poses from sampled end-effector positions | Check actual grasp/placement affordances, support relations, and all required task phases | +| Combined Expansion | Reference families, candidate composition, and scheduling contracts; current scene randomization is limited to cube initial states with fixed drop targets | Accept general scene variant sources while retaining compatibility with existing profiles | +| Task Program integrations and Gym bridge | Compilation, live binding, Atomic Skill execution, and demo integration | Refresh bindings after preparing a scene variant and connect shared execution and acceptance interfaces | +| Task Engine in PR #729 | Scene binding, feasibility assessment, semantic planning, bundles, and execution reports | Rebuild geometry-dependent artifacts for each new layout and connect the shared expansion workflow | +| Workspace feasibility in the PR | Declare runtime checks for phases such as pickup, handover, and placement | Consume actual workspace/IK check results while preserving the distinction between verified and unverified outcomes | + +Kinematic reachability, planning success, program completion, physical task success, and data persistence are separate outcomes. Neither workspace caches nor trajectory motion-limit checks replace full task acceptance. + +## 3 Module Ownership and Dependency Boundaries + +| Module | Responsibilities | Responsibilities outside its ownership | +| --- | --- | --- | +| **New `lab.sim.scene_expansion`** | Scene candidate contracts, layout sampling, distractor placement, deployment candidates, and spatial constraint screening | Parsing TaskSpec/Task Program, model calls, reset/step, full task acceptance, and writing datasets | +| `lab.sim.motion.workspace` | Kinematic caches, end-effector sampling, and manipulability metrics | Scene materialization, collision-world maintenance, and task success decisions | +| `lab.task_program.integrations` | Translate task requirements into expansion inputs; connect scene preparation, binding refresh, program preparation, and acceptance | A separate compiler/runtime or physical execution loop | +| `lab.task_program.semantics` and the existing runtime | Roles, affordances, call/effect contracts, and execution | Scene generation pipelines and asset production | +| Atomic Skills and MotionGenerator | Live action planning, IK, collision planning, and execution policies | Changing task goals or relaxing success criteria | +| Gym host | Environment construction, reset, settling/restoring initial state, step, observations, and demo recording | Task Program schemas, compilers, or layout generation algorithms | +| GenSim Scene Engine | Asset materialization, scene editing, scene revisions, and import/export | The shared action executor and full task acceptance | +| GenSim Task Engine | TaskSpec adaptation, semantic binding validation, and graph/program/bundle regeneration | A separate simulation, recording, or expansion session lifecycle | +| Existing motion expansion and data pipeline | Trajectory candidates, budgets, coverage, receipts, and persistence | Implicit scene changes inside trajectory operators | + +### Why Retain a Dedicated scene_expansion Module + +Layouts, support relations, distractors, and deployment candidates can be expressed through entity geometry and spatial constraints. Their responsibilities differ from trajectory transformations within a fixed scene. A dedicated module allows both entry points to share algorithms without making handwritten tasks depend on GenSim models, asset generation services, or the full scene editing pipeline. + +`scene_expansion` accepts explicit geometry, poses, constraints, and workspace query interfaces. It does not import TaskSpec, Task Program, or Gym. Task roles are translated into entity references and spatial requirements; task semantics must not be inferred from entity names. Integration adapters supply simulation state and robot queries. + +Do not create another authoritative Scene, SceneGraph, or runtime registry. Candidates describe changes to an existing scene, while entity identity and execution bindings continue to use existing configurations, Scene Engine, and `SceneRegistry`. Any shared numerical algorithms extracted into `compute` must accept numerical data only and must not depend on `lab`. + +## 4 Minimum Public Contracts + +The following are proposed interface responsibilities. Field names will be finalized in P0; existing contracts remain with their current owners. + +| Contract | Minimum content | Owner | +| --- | --- | --- | +| `SceneExpansionCfg` | Movable entities, sampling regions, distractor pool/count, deployment bounds, seed, and candidate/retry budgets | `scene_expansion`, using `@configclass` | +| `SceneVariant` | Parent scene identity, entity pose changes, added asset references, robot deployment, and candidate seed/provenance | `scene_expansion` | +| Task constraint adapter | Fixed roles, permitted changes, initial-state relations, manipulation pose requirements, and validator references | Task Program / GenSim integrations | +| Workspace query adapter | Sampling, frame transforms, and candidate information for a specified robot and control part | Simulation integrations and existing workspace facilities | +| Scene preparation result | Final entity set, settled initial state, and binding/asset/deployment identities | Host, with upstream materialization input from GenSim | +| Check and execution results | Stage, status, reason, evidence references, actual trajectory, and persistence outcome | Existing validation, execution, and recording contracts, with metadata extensions as needed | + +Maintain three identities: task identity, scene instance identity, and execution attempt identity. GenSim references the TaskSpec semantic hash. Handwritten tasks reference explicit semantic declarations and validator versions; a program source hash alone is not a task semantic identity. + +Candidate ordinals and seeds support proposal reproduction; settled initial-state snapshots support execution restoration. Initial-state and attempt identities must not depend on temporary physical slot/env_id assignments. Record object/robot state, asset content identities, role bindings, program and integration versions, control period, planning policy, and validator version. A seed alone does not promise bitwise reproduction of physical trajectories across backends or versions. + +## 5 Workspace and Scene Expansion Integration Points + +### Candidate Sampling + +With a fixed robot, sample object poses within permitted support surfaces and spatial regions, then derive actual manipulation targets from object-local affordances. For example: + +`T_arena_tcp = T_arena_object × T_object_grasp` + +Reachable TCP samples may also be used to derive candidate object poses, but the results must still satisfy support, orientation, and other spatial constraints. Changing a sample's height or orientation invalidates any assumption that its original reachability still applies. + +Workspace data guides proposals, supplies IK seeds, and ranks candidates. A pose missing from a sparse cache remains pending verification and is evaluated using IK for the target pose. Consumers must respect the validity mask for invalid samples. Corrupt caches or configuration errors must fail explicitly rather than being ignored. + +All transforms must distinguish the local arena, robot/control-part base, and object-local frames. Scene Engine's Y-up editing state is converted to Z-up at the existing export boundary; expansion must not convert already normalized deployment poses again. + +### Task Phase Screening + +Check grasp, pre-grasp, lift, placement, and retreat targets, as well as motion between phases. Individual arm reachability does not establish a simultaneous handover: compatible poses and collisions between the robots must also be checked. Later articulated tasks require checks across the complete manipulation travel. + +Recheck the actual initial state after settling. Whenever held-object or articulation state changes during execution, each plan/replan reads fresh state. An initial workspace check is not a certificate for the entire episode. + +### Robot Deployment + +P4 performs bounded sampling of base XY/yaw within declared mounting regions and derives height from mounting rules. The relative mounting arrangement of the two arms is preserved by default. Rank candidates by coverage and manipulation quality across all task phases, then run IK, motion planning, and physical validation. Explicit arm assignments and resource constraints take precedence and must not be rewritten to improve acceptance rates. + +This selects deployments between episodes; it does not control a mobile base during execution. + +### Collision and Binding Refresh + +| Change | Required refresh | +| --- | --- | +| Existing object poses | Live state, pose-dependent targets, and planning snapshots; movable entities use collision declarations that support updates | +| Added distractors | Physical entities, relevant registries/providers, and the complete collision world | +| Base deployment changes | FK/IK transforms, robot-relative obstacle poses, and affected sensor state | +| Integration declaration changes | Fingerprints, compiled artifacts that depend on the old declarations, and runtime assembly | + +The initial version reconstructs the environment when the entity set changes; it does not require dynamic entity insertion or deletion during execution. Local collision checks in specific GenSim routes do not establish equivalent support for every route. A selected route that lacks the required checks or replanning capability must report that limitation explicitly. + +## 6 Preserving Semantics and Constraining Distractors + +Fix roles and required relations first, sample layouts second, and add distractors last. Required initial conditions include support, containment, and open/closed articulation states. Goals and invariants during execution follow explicit task requirements. + +- Distractors must exist in both the physical scene and the planning collision world. Check support, overlap, stability, and required manipulation space. +- Distractors may block the original path. Reject a candidate if no new path satisfying the task constraints can be found. +- A task referring to "all red blocks" cannot retain its old target set after another red block is added. A moved "leftmost cup" must still satisfy that description. Reject such semantic changes rather than changing task meaning to accept the variant. +- Make the instruction's reference frame explicit. Changing robot deployment must not silently reinterpret relations such as "left" or "in front." +- Do not repair failures by replacing bound objects, narrowing the target set, or lowering thresholds. GenSim model suggestions must pass the original constraint checks. +- If the initial state already satisfies a goal that should be achieved through execution, reject it according to the task's initial-state requirements to avoid invalid expert demonstrations. + +## 7 Complete Expert Trajectory Generation Workflow + +```text +Handwritten program + scene + constraints GenSim TaskSpec + scene + program generation + \ / + Scene expansion request + | + scene_expansion candidates <- workspace queries + | + Semantic/geometric prechecks and materialization + | + Host instantiation, reset, and settling + | + Actual initial-state checks, binding/artifact refresh + | + Shared Task Program -> Atomic Skills -> Motion / IK + | + Gym execution, observations, trajectory recording + | + Measured task acceptance + / \ + Failure classification Persist successful trajectory + and bounded retry | + Optional within-scene expansion + | + Restore, execute, independently validate +``` + +1. **Prepare inputs**: fix task identity, base scene, permitted changes, validators, and budgets. +2. **Generate candidates**: sample layouts, distractors, and optional deployments, then run inexpensive prechecks. +3. **Prepare the actual scene**: materialize assets, construct/restore the environment, and let it settle. Physics configuration remains owned by the corresponding environment component; expansion profiles do not override physics/backend selection. +4. **Confirm initial state and program**: recheck actual state, refresh scene bindings, collision worlds, and geometry-dependent program artifacts. If this process changes the deployment again, settle, inspect, and capture the initial state again. +5. **Execute and record**: begin recording from the confirmed initial state. The shared Task Program runtime produces actions through Atomic Skills and motion planning; the Gym demo executor calls `env.step()`. The bridge does not advance physics directly, and the compiler does not read live simulation state. +6. **Validate and persist**: preserve separate results for runtime completion, measured task outcome, and dataset acceptance/commit. Send accepted successful trajectories to the existing sink under the shared acceptance rules. +7. **Optionally expand trajectories**: use a successful execution as the scene's reference. Each variant restores the same initial state, executes again, and passes independent validation; it cannot inherit the reference's success result. + +### Handwritten Task Program Integration + +The author supplies the program/integration, scene expansion configuration, initial-state constraints, and measured acceptance criteria. Object-local affordances and explicit relative targets follow object changes; absolute targets remain fixed. + +Program structure may be reused when only poses change and declarations remain unchanged, with a fresh runtime bridge. Update the relevant compiled artifacts when declarations, fingerprints, or geometric constants change. Apply scene changes during reset/preparation; final observations and the first recorded frame must correspond to the actual settled initial state. + +### GenSim Integration + +Task Engine fixes the TaskSpec and target roles, and Scene Engine produces independent candidate revisions. After materialization, settling, and final inspection, prepare the layout-dependent semantic graph, program, constraints, and bundle. + +A temporary deployment may be assembled for simulation preparation, but the actual execution attempt must use artifacts consistent with the final scene and bindings. Recompute layout-dependent choices such as arm selection, handover points, placement targets, and articulation operation order. Record changes introduced by existing geometric/physical adaptation and recheck the final deployment; execution evidence applies only to that deployment. + +Preserve the task-route support boundaries of PR #729. Workspace integration does not automatically qualify unsupported E7/E8 or other routes for execution. + +## 8 Acceptance, Retries, and Data Coverage + +Accepting an expert episode requires a valid initial state, valid program execution, measured terminal state, required intermediate events, and constraints maintained during execution. Missing required observations produce an unverified result. Projected effects cannot replace required physical evidence. Passing discrete collision screening does not establish continuous collision freedom. + +Use consistent failure stages: semantic mismatch, geometry/settling failure, IK failure, planning failure, execution failure, acceptance failure, insufficient evidence, and persistence failure. Do not hide configuration, schema, or interface errors as ordinary candidate rejections. + +The host coordination layer bounds candidate generation and execution attempts. Retain existing action recovery policies, but count nested recovery toward the total budget. New independent execution attempts start from the saved initial state. Scene Engine, Task Engine, and the execution layer must not independently add unbounded retries or relax requirements. + +Reuse existing session, budget, coverage, receipt, and persistence mechanisms instead of creating a competing generation state machine. Scene expansion supplies new scene cases, initial states, and reference families to the existing workflow. The host still owns physical restoration and writes. Record scene-level and trajectory-level attempts separately, and increment persisted coverage only after a persistence receipt confirms the write. + +Track the following separately: + +- Scene coverage: workspace region, target distance, distractor count/obstruction level, and robot deployment pose. +- Within-scene trajectory coverage: valid motion variants from the same initial state. +- Generation efficiency: rejection rates by stage, planning/execution cost, measured success rate, and confirmed persisted episode count. + +Success evidence establishes only that this exact scene and execution passed the declared checks. It does not establish success for nearby positions, other seeds, or the original unadapted scene. + +## 9 Three Developer Responsibilities and Collaboration Interfaces + +Responsibilities are assigned by role. Map them to GitHub assignees during implementation; no colleague account is assumed. + +| Owner | Primary responsibility | Main deliverables | +| --- | --- | --- | +| Shared framework lead (proposal author) | Module architecture, public contracts, workspace and execution integration | `scene_expansion` interfaces; workspace/deployment evaluation adapters; binding/collision refresh; Gym execution/recording integration | +| Task Engine developer | Task semantics, program generation, and task acceptance | Role/constraint adapters; handwritten baseline task and validators; GenSim graph/program/bundle regeneration | +| Scene Engine developer | Shared scene algorithms and the GenSim scene backend | Layout/support/distractor algorithms in `scene_expansion`; asset materialization, revisions, export, and mounting-region candidates | + +The shared framework lead owns the `scene_expansion` module; the Scene Engine developer owns its scene algorithms. Shared code does not import the full GenSim pipeline. + +In P0, the shared framework lead authors the interface PR. Both colleagues confirm four boundaries: task constraint inputs, scene candidates, scene preparation results, and check/execution results. Agree on frames, entity identities, initial state, budgets, and invalidation rules before implementing separate module files in parallel. + +| Cross-module decision | Accountable owner | Collaborator inputs | +| --- | --- | --- | +| Reachability and deployment feasibility | Shared framework lead | Scene Engine supplies candidates; Task Engine supplies manipulation phase requirements | +| Preservation of original task semantics | Task Engine developer | Actual scene entities, spatial relations, and runtime observations | +| Support/layout geometric validity | Scene Engine developer | Shared framework lead supplies physical settling results; geometric and physical conclusions are recorded separately | +| Environment reconstruction and collision refresh | Shared framework lead | Scene Engine supplies added assets and poses | +| Program/bundle invalidation and regeneration | Task Engine developer | Scene and integration identity changes | +| Retry scheduling and persistence | Shared framework lead | Both engines return structured results without rewriting goals | + +Merge the interface PR first, then develop the shared runtime, scene algorithms, and task semantics in parallel in separate files. Task Engine provides handwritten task acceptance from P0. Scene Engine provides candidates independent of the full generation pipeline from P1. Neither developer needs to wait until P3 to begin. + +## 10 Staged Implementation and Acceptance + +| Stage | Shared framework lead | Task Engine developer | Scene Engine developer | Stage acceptance | +| --- | --- | --- | --- | --- | +| P0 Contracts and baseline | Public interfaces, host integration, result recording | Handwritten Pick/Place, role constraints, measured acceptance | Fixed scene and deterministic candidate examples | Behavior is unchanged with expansion disabled; program completion cannot accept a physical failure | +| P1 Layout expansion | Workspace, pose IK, settling/binding refresh | Relative/absolute targets and initial-state semantics | Position/yaw sampling on support surfaces, boundaries, and spacing | Replan and complete tasks across layouts; identify reachable object centers with unreachable manipulation poses | +| P2 Distractors | Environment reconstruction, collision updates, bounded replanning | Referential ambiguity, target-set changes, failure classification | Fixed asset pool and distractor placement | Route around obstacles on the original path; fail within budget for complete blockage; reject semantic changes | +| P3 GenSim integration | Shared execution, acceptance, and recording for both sources | TaskSpec and graph/program/bundle regeneration | Materialization, revisions, final-inspection inputs, and export | Both entry points work; stale bindings, planning worlds, and evidence are not reused incorrectly | +| P4 Deployment search | FK/IK for base candidates and whole-task scoring | Arm selection, phase, and handover constraints | Mounting regions, support conditions, deployment candidates | Correct frames and collisions; preserve relative mounting and explicit resource constraints | +| P5 Coverage and throughput | Trajectory expansion, coverage, compatible batching | Dual-arm/articulated tasks and acceptance | Complex support relations, scene caching, more distractors | Independent trajectory acceptance; correct collision worlds per row; separate coverage and cost reporting | + +P0–P3 keep B=1, the robot base, and the physics backend fixed. The first joint milestone is P2: a handwritten Task Program produces trajectories with measured success in new layouts guided by workspace data, including distractors that may block the original path. The second milestone is P3: GenSim uses the same execution and data acceptance path. + +Later batching combines only compatible entity sets, initial-state representations, control periods, backends, and recording schemas. Rows with different robot-relative layouts require matching collision worlds. Physical settling must not unintentionally advance other rows that are not participating in reset. + +## 11 Validation Plan + +| Layer | Required cases | +| --- | --- | +| Pure contracts and candidates | Seed/ordinal reproduction, budget exhaustion, stable entity identity, explicit invalid-configuration failures, invalid workspace sample handling | +| Spatial and semantic checks | Support boundaries, overlap, frame conversion, rechecking modified height/orientation, changes to all/count/leftmost references | +| Reachability and planning | Reachable object center but unreachable grasp; individually reachable arms that collide jointly; replanning around a blocked original path; complete blockage | +| Lifecycle | Settling outside the allowed region; first frame matches actual initial state; initial-state restoration; binding/collision updates after entity or base changes | +| Task acceptance | Program completion with failed goals; incorrect intermediate-event order; missing evidence; later actions invalidating earlier outcomes that must persist | +| Both entry points | Consistent result contracts for equivalent handwritten and GenSim goals; each GenSim variant's bundle matches its final scene | +| Persistence | Failures do not enter the success set; failed commits do not increment confirmed coverage; trajectories and evidence reference the correct scene and attempt | + +Add focused production-contract tests at each stage, followed by small physical simulation runs across multiple seeds. Report candidate acceptance rates, measured success rates, and costs separately. Do not add tests whose primary subject is a tutorial; examples exercise the underlying production interfaces. + +During implementation, follow the project's `/add-test`, `/pre-commit-check`, and public API documentation workflows, and review affected project context. The current change is a target design only; do not register unimplemented modules as existing runtime capabilities. + +## 12 Dependencies and Related Discussions + +- [PR #729](https://github.com/DexForce/EmbodiChain/pull/729): foundation for GenSim Task Engine integration. P0–P2 for handwritten Task Programs can proceed first; GenSim integration adapts to the interfaces that ultimately land. +- [Issue #670](https://github.com/DexForce/EmbodiChain/issues/670): existing general expert generation and expansion lifecycle. This proposal adds scene sources that preserve task semantics without replacing session/execution/persistence boundaries. +- [Issue #655](https://github.com/DexForce/EmbodiChain/issues/655): related discussion of task goals, physical evaluation, and environment variation. This proposal focuses on Task Program expert data generated through scene expansion. +- [Issue #709](https://github.com/DexForce/EmbodiChain/issues/709): real2sim trajectory integration, a separate source outside this proposal's scope. + +This proposal does not require moving the existing TaskSpec into a public package first or reorganizing existing motion expansion. Share capabilities through adapters, preserve compatibility, and review new implementation work independently in the stages above. diff --git a/docs/source/api_reference/embodichain/embodichain.lab.sim.rst b/docs/source/api_reference/embodichain/embodichain.lab.sim.rst index 2c035104e..b9aa4fcad 100644 --- a/docs/source/api_reference/embodichain/embodichain.lab.sim.rst +++ b/docs/source/api_reference/embodichain/embodichain.lab.sim.rst @@ -29,6 +29,7 @@ utilities that wire all of these together. robots sensors motion + scene_expansion atomic_actions types utility @@ -130,6 +131,14 @@ Atomic Actions embodichain.lab.sim.atomic_actions +Scene Expansion +--------------- + +.. toctree:: + :maxdepth: 1 + + embodichain.lab.sim.scene_expansion + Shared Types ------------ diff --git a/docs/source/api_reference/embodichain/embodichain.lab.sim.scene_expansion.rst b/docs/source/api_reference/embodichain/embodichain.lab.sim.scene_expansion.rst new file mode 100644 index 000000000..e6d1a1293 --- /dev/null +++ b/docs/source/api_reference/embodichain/embodichain.lab.sim.scene_expansion.rst @@ -0,0 +1,119 @@ +embodichain.lab.sim.scene_expansion +=================================== + +Scene expansion describes changes to a base scene before a new task execution. +The initial API provides immutable existing-entity pose proposals and a bounded +source of authored candidates. Task Program and GenSim hosts can share these +contracts without introducing their execution or generation dependencies into +the proposal package. + +The core owns no physical restoration, task semantics, simulator stepping, +episode acceptance, or dataset writes. Public imports use the normal +``embodichain.lab.sim`` initialization and require the simulation dependencies; +constructing these values creates no simulation world. + +.. currentmodule:: embodichain.lab.sim.scene_expansion + +.. autosummary:: + :nosignatures: + + SceneExpansionCfg + ScenePoseChange + SceneVariant + FixedSceneCandidateSource + +Frames and proposal identity +---------------------------- + +``ScenePoseChange.arena_pose`` is a proper homogeneous 4x4 transform from the +entity frame into its local Z-up arena. It excludes replication offsets and +does not use a robot chain-root frame. Nested numeric input is copied into +immutable tuples; nonfinite, reflected, nonorthonormal, or malformed transforms +are rejected. + +``SceneVariant.variant_id`` includes the original scene identity, seed, +ordinal, proposed geometry, and provenance. Entity and provenance ordering is +canonicalized. A proposal ID is separate from the actual initial-state identity +captured after physical settling, and neither certifies task completion. +The seed reproduces a proposal schedule; it does not promise identical physics +across engines or versions. + +Bounded fixed candidates +------------------------ + +``FixedSceneCandidateSource`` snapshots at most ``max_candidates`` entries from +its input iterable. It leaves later entries untouched, permits changes only to +declared movable entity UIDs, and returns the same immutable candidates on each +iteration. An empty pose-change tuple is an explicit nominal candidate; an +empty source generates no candidates. + +.. code-block:: python + + from embodichain.lab.sim.scene_expansion import ( + FixedSceneCandidateSource, + SceneExpansionCfg, + ScenePoseChange, + ) + + source = FixedSceneCandidateSource( + SceneExpansionCfg(seed=7, max_candidates=1, movable_entity_ids=("cube",)), + parent_scene_id="pick-place-scene:revision-1", + candidates=[ + ( + ScenePoseChange( + entity_uid="cube", + arena_pose=( + (1, 0, 0, 0.35), + (0, 1, 0, 0.10), + (0, 0, 1, 0.76), + (0, 0, 0, 1), + ), + ), + ), + ], + metadata=(("source", "authored-layout"),), + ) + variant = source.variants[0] + +This only constructs a proposal. Support, manipulation reachability, collision +checks, and task semantics remain explicit host responsibilities. To evaluate +one proposal through the shared Task Program executor, use +:mod:`embodichain.lab.task_program.integrations.scene_expansion`. + +Public contracts +---------------- + +.. autoclass:: SceneExpansionCfg + :members: + :exclude-members: __init__, copy, replace, to_dict, validate + +.. autoclass:: ScenePoseChange + :members: + +.. autoclass:: SceneVariant + :members: + +.. autoclass:: FixedSceneCandidateSource + :members: + +Implementation module exports +----------------------------- + +.. currentmodule:: embodichain.lab.sim.scene_expansion.cfg + +.. autosummary:: + + SceneExpansionCfg + +.. currentmodule:: embodichain.lab.sim.scene_expansion.contracts + +.. autosummary:: + + ScenePoseChange + SceneVariant + +.. currentmodule:: embodichain.lab.sim.scene_expansion.source + +.. autosummary:: + + FixedSceneCandidateSource diff --git a/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.rst b/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.rst index e9fbcf27d..5d6be4f29 100644 --- a/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.rst +++ b/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.rst @@ -57,6 +57,11 @@ The provider-independent language and compiler live in registration, and simulation assembly. Gym stepping remains in :mod:`embodichain.lab.gym.envs.task_program`. +.. toctree:: + :maxdepth: 1 + + embodichain.lab.task_program.integrations.scene_expansion + .. autoclass:: TaskProgramEnvironmentAdapter :members: diff --git a/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.scene_expansion.rst b/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.scene_expansion.rst new file mode 100644 index 000000000..e5f3cbcaa --- /dev/null +++ b/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.scene_expansion.rst @@ -0,0 +1,119 @@ +embodichain.lab.task_program.integrations.scene_expansion +========================================================= + +This integration evaluates one +:class:`~embodichain.lab.sim.scene_expansion.SceneVariant` through the existing +Task Program and Gym demonstration execution path. An injected host prepares +the physical scene; a separate validator supplies measured task evidence. +The API currently requires a single environment row and an explicit or +environment-configured Task Program. + +.. currentmodule:: embodichain.lab.task_program.integrations.scene_expansion + +.. autosummary:: + :nosignatures: + + ScenePreparationResult + SceneVariantEpisodeResult + execute_scene_variant + +Preparation and execution boundary +---------------------------------- + +The preparation callback applies or restores the candidate, settles physics, +checks the actual initial state, saves its initial-state artifact, and refreshes +recording observations and affected bindings. It returns a +``ScenePreparationResult`` with the actual initial-state ID and nonempty +``ValidationResult`` checks. Rejecting that initial state prevents execution. +Physical preparation and workspace screening are supplied by the caller at +this layer; the fixed-candidate API does not instantiate a simulation host. + +For valid initial states, ``execute_scene_variant`` creates a fresh bridge using +the canonical ``EmbodiedEnv.create_demo_segments(task_program=...)`` path and +passes the resulting segments through ``execute_demo_episode``. Handwritten +segment-planner overrides do not replace the selected Task Program. The normal +Gym executor owns ``env.step()`` and suspends automatic reset during execution. + +Measured acceptance and persistence +----------------------------------- + +``SceneVariantEpisodeResult`` preserves initial checks, program execution, and +measured task checks independently. Acceptance requires all three to pass. +Empty checks, unavailable evidence, a failed program, or failed measured goals +cannot accept the scene. A completed program with projected effects still +needs physical evidence from the measured validator. + +The attempt's proposal, actual initial-state identity, and validation evidence +are published under ``scene_expansion`` in the existing episode metadata. +Preparation provenance is owned and recursively immutable; ``to_metadata()`` +returns JSON-compatible values. Failed or interrupted attempts remain +ineligible for reset-time dataset, camera, and trajectory persistence, including +explicit commit-row requests and failed-episode recording. + +The integration performs one attempt, propagates configuration and host errors, +and creates no retry scheduler or persistence session. The caller closes the +normal transaction with ``env.reset(options={"save_data": result.accepted})``. +An accepted execution is not confirmation that a dataset write succeeded. + +Injecting a host and measured validator +--------------------------------------- + +The following helper accepts the fixed source from the scene proposal example. +Both callbacks are required application implementations, so the example does +not substitute a constant passing check for physical evidence: + +.. code-block:: python + + from collections.abc import Callable + + from embodichain.lab.gym.envs.embodied_env import EmbodiedEnv + from embodichain.lab.sim.motion.expansion import ValidationResult + from embodichain.lab.sim.scene_expansion import ( + FixedSceneCandidateSource, + SceneVariant, + ) + from embodichain.lab.task_program.integrations.scene_expansion import ( + ScenePreparationResult, + SceneVariantEpisodeResult, + execute_scene_variant, + ) + + def collect_fixed_variants( + env: EmbodiedEnv, + source: FixedSceneCandidateSource, + *, + prepare_scene: Callable[[SceneVariant], ScenePreparationResult], + measured_validator: Callable[[], ValidationResult], + ) -> list[SceneVariantEpisodeResult]: + results = [] + for variant in source: + result = execute_scene_variant( + env, + variant, + prepare_scene=prepare_scene, + measured_validator=measured_validator, + episode_index=variant.ordinal, + attempt_id=variant.ordinal, + ) + results.append(result) + env.reset(options={"save_data": result.accepted}) + return results + +The environment must already have its Task Program integration configured. +``prepare_scene`` begins each candidate from a clean recording boundary using +reset with ``save_data=False`` when needed. It captures state and reseeds +observations only after settling and refreshing bindings. The measured callback +reads terminal state and any required intermediate-event evidence; it returns +``ValidationResult`` rather than an arbitrary truthy value. Applications retain +their own bounded retry policy and exceptional-attempt cleanup. + +Public interfaces +----------------- + +.. autoclass:: ScenePreparationResult + :members: + +.. autoclass:: SceneVariantEpisodeResult + :members: + +.. autofunction:: execute_scene_variant diff --git a/embodichain/lab/gym/envs/demo.py b/embodichain/lab/gym/envs/demo.py index 67bdb0aad..29518cfdf 100644 --- a/embodichain/lab/gym/envs/demo.py +++ b/embodichain/lab/gym/envs/demo.py @@ -649,25 +649,29 @@ def resolve_demo_segments(env: Any, **kwargs: Any) -> Iterable[DemoSegment]: else (DemoSegment(actions, name="legacy", metadata={"segment_count": 1}),) ) + return _validated_demo_segments(env, segments) + + +def _validated_demo_segments( + env: Any, + segments: Iterable[DemoSegment] | DemoSegment | None, +) -> Iterable[DemoSegment]: + """Apply the same segment and instruction contract to every plan source.""" if segments is None: - return () + return if isinstance(segments, DemoSegment): segments = (segments,) fallback_instruction = _dataset_instruction(env) - - def _validate() -> Iterable[DemoSegment]: - for segment in segments: - if not isinstance(segment, DemoSegment): - raise TypeError( - "create_demo_segments() must yield DemoSegment objects, " - f"got {type(segment).__name__}." - ) - if segment.instruction is None: - segment = replace(segment, instruction=fallback_instruction) - yield segment - - return _validate() + for segment in segments: + if not isinstance(segment, DemoSegment): + raise TypeError( + "create_demo_segments() must yield DemoSegment objects, " + f"got {type(segment).__name__}." + ) + if segment.instruction is None: + segment = replace(segment, instruction=fallback_instruction) + yield segment def execute_demo_episode( @@ -678,6 +682,7 @@ def execute_demo_episode( attempt_id: int = 0, should_stop: StopPredicate | None = None, progress: ProgressWrapper | None = None, + segments: Iterable[DemoSegment] | None = None, **plan_kwargs: Any, ) -> DemoEpisodeResult: """Plan and execute every segment in one environment episode. @@ -695,6 +700,11 @@ def execute_demo_episode( attempt_id: Zero-based identifier for this collection attempt. should_stop: Optional callback checked before every action. progress: Optional wrapper such as ``tqdm`` for action iterables. + segments: Optional trusted segment iterable. When provided, bypasses + task planner selection while retaining ordinary segment validation, + instruction fallback, execution, and recording. An empty iterable + produces an empty-plan failure. Otherwise planning methods are + selected as usual. **plan_kwargs: Arguments forwarded to the task's planning method. Returns: @@ -757,7 +767,11 @@ def publish_active_mask() -> None: try: segment_count = 0 - segments = iter(resolve_demo_segments(env, **plan_kwargs)) + segment_iterator = iter( + resolve_demo_segments(env, **plan_kwargs) + if segments is None + else _validated_demo_segments(env, segments) + ) while any(active): if should_stop is not None and should_stop(): fatal_reason = "interrupted" @@ -768,7 +782,7 @@ def publish_active_mask() -> None: publish_active_mask() break try: - segment = next(segments) + segment = next(segment_iterator) except StopIteration: break diff --git a/embodichain/lab/gym/envs/embodied_env.py b/embodichain/lab/gym/envs/embodied_env.py index 98da9bb73..03f2c483c 100644 --- a/embodichain/lab/gym/envs/embodied_env.py +++ b/embodichain/lab/gym/envs/embodied_env.py @@ -1034,6 +1034,22 @@ def _initialize_episode( "commit_env_ids must be a subset of the rows being reset." ) + # Scene acceptance is independent of Task Program completion. A + # rejected or interrupted single-row expansion attempt must not become + # saveable through a later reset, even with failed-episode recording or + # explicit commit rows enabled. Reset consumes and clears this metadata. + episode_metadata = getattr(self, "_demo_episode_metadata", None) + if self.num_envs == 1 and episode_metadata: + scene_expansion = episode_metadata[0].get("scene_expansion") + if ( + isinstance(scene_expansion, Mapping) + and scene_expansion.get("accepted") is not True + ): + save_data = False + env_ids_to_commit = torch.empty( + 0, device=status_device, dtype=torch.long + ) + # Save dataset before clearing buffers for environments that are being reset if env_ids_to_commit.numel() > 0 and self.dataset_manager: if "save" in self.dataset_manager.available_modes: diff --git a/embodichain/lab/sim/scene_expansion/__init__.py b/embodichain/lab/sim/scene_expansion/__init__.py new file mode 100644 index 000000000..cfe352cf6 --- /dev/null +++ b/embodichain/lab/sim/scene_expansion/__init__.py @@ -0,0 +1,34 @@ +# ---------------------------------------------------------------------------- +# Copyright (c) 2021-2026 DexForce Technology Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +"""Scene proposal contracts and candidate sources shared by simulation hosts. + +Candidates describe arena-local changes. Task semantics, scene materialization, +reset, execution, task acceptance, and persistence belong to host integrations. +""" + +from __future__ import annotations + +from .cfg import SceneExpansionCfg +from .contracts import ScenePoseChange, SceneVariant +from .source import FixedSceneCandidateSource + +__all__ = [ + "SceneExpansionCfg", + "ScenePoseChange", + "SceneVariant", + "FixedSceneCandidateSource", +] diff --git a/embodichain/lab/sim/scene_expansion/cfg.py b/embodichain/lab/sim/scene_expansion/cfg.py new file mode 100644 index 000000000..7b8f54831 --- /dev/null +++ b/embodichain/lab/sim/scene_expansion/cfg.py @@ -0,0 +1,63 @@ +# ---------------------------------------------------------------------------- +# Copyright (c) 2021-2026 DexForce Technology Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +"""Configuration of bounded, explicit scene proposals.""" + +from __future__ import annotations + +from embodichain.utils import configclass + +from .contracts import _identifier, _nonnegative_integer + +__all__ = ["SceneExpansionCfg"] + + +@configclass +class SceneExpansionCfg: + """Limits and permissions for a scene candidate source. + + Args: + seed: Nonnegative proposal-generation seed; not a physical replay seed. + max_candidates: Positive maximum candidates consumed from the source. + movable_entity_ids: Unique physical UIDs allowed to change pose. An + empty tuple permits only nominal candidates with no pose changes. + + Execution and persistence budgets remain owned by the host integration. + Calling a scene expansion integration opts in; ordinary execution paths + do not consult this configuration. + """ + + seed: int = 0 + max_candidates: int = 1 + movable_entity_ids: tuple[str, ...] = () + + def __post_init__(self) -> None: + _nonnegative_integer(self.seed, "seed") + _nonnegative_integer(self.max_candidates, "max_candidates") + if self.max_candidates == 0: + raise ValueError("max_candidates must be positive") + if isinstance(self.movable_entity_ids, str): + raise ValueError("movable_entity_ids must be a sequence of entity UIDs") + try: + self.movable_entity_ids = tuple(self.movable_entity_ids) + except TypeError as exc: + raise ValueError( + "movable_entity_ids must be a sequence of entity UIDs" + ) from exc + for uid in self.movable_entity_ids: + _identifier(uid, "movable_entity_ids entry") + if len(set(self.movable_entity_ids)) != len(self.movable_entity_ids): + raise ValueError("movable_entity_ids must contain unique entity UIDs") diff --git a/embodichain/lab/sim/scene_expansion/contracts.py b/embodichain/lab/sim/scene_expansion/contracts.py new file mode 100644 index 000000000..c3db005ed --- /dev/null +++ b/embodichain/lab/sim/scene_expansion/contracts.py @@ -0,0 +1,189 @@ +# ---------------------------------------------------------------------------- +# Copyright (c) 2021-2026 DexForce Technology Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +"""Immutable scene proposals, independent of task and execution lifecycles.""" + +from __future__ import annotations + +from dataclasses import dataclass +import hashlib +import json +import math +from numbers import Real + +__all__ = ["ScenePoseChange", "SceneVariant"] + + +def _identifier(value: str, name: str) -> str: + if not isinstance(value, str) or not value or value.strip() != value: + raise ValueError(f"{name} must be a nonempty stripped string") + return value + + +def _nonnegative_integer(value: int, name: str) -> int: + if type(value) is not int or value < 0: + raise ValueError(f"{name} must be a nonnegative integer") + return value + + +def _rigid_pose(value: object) -> tuple[tuple[float, ...], ...]: + try: + rows = tuple(tuple(row) for row in value) # type: ignore[union-attr] + except TypeError as exc: + raise ValueError("arena_pose must be a 4x4 rigid transform") from exc + if len(rows) != 4 or any(len(row) != 4 for row in rows): + raise ValueError("arena_pose must be a 4x4 rigid transform") + if any( + isinstance(item, bool) or not isinstance(item, Real) or not math.isfinite(item) + for row in rows + for item in row + ): + raise ValueError("arena_pose must contain finite real numbers") + pose = tuple( + tuple(float(item) if item != 0 else 0.0 for item in row) for row in rows + ) + tolerance = 1e-6 + if any( + abs(pose[3][index] - target) > tolerance + for index, target in enumerate((0, 0, 0, 1)) + ): + raise ValueError("arena_pose must have homogeneous last row [0, 0, 0, 1]") + rotation = tuple(row[:3] for row in pose[:3]) + if any( + abs(sum(rotation[k][i] * rotation[k][j] for k in range(3)) - (i == j)) + > tolerance + for i in range(3) + for j in range(3) + ): + raise ValueError("arena_pose rotation must be orthonormal") + determinant = ( + rotation[0][0] + * (rotation[1][1] * rotation[2][2] - rotation[1][2] * rotation[2][1]) + - rotation[0][1] + * (rotation[1][0] * rotation[2][2] - rotation[1][2] * rotation[2][0]) + + rotation[0][2] + * (rotation[1][0] * rotation[2][1] - rotation[1][1] * rotation[2][0]) + ) + if abs(determinant - 1.0) > tolerance: + raise ValueError("arena_pose rotation must be proper (determinant +1)") + return (*pose[:3], (0.0, 0.0, 0.0, 1.0)) + + +@dataclass(frozen=True) +class ScenePoseChange: + """A proposed pose for one existing physical entity. + + Args: + entity_uid: Stable physical entity UID, independent of environment slots. + arena_pose: Homogeneous 4x4 transform from the entity frame into its + local Z-up arena frame. Inputs are copied into immutable tuples. + + .. attention:: + An arena pose excludes replication offsets. The host converts it to + backend coordinates when applying the candidate. This contract does + not establish support, collision freedom, or manipulation feasibility. + """ + + entity_uid: str + arena_pose: tuple[tuple[float, ...], ...] + + def __post_init__(self) -> None: + _identifier(self.entity_uid, "entity_uid") + object.__setattr__(self, "arena_pose", _rigid_pose(self.arena_pose)) + + +@dataclass(frozen=True) +class SceneVariant: + """An immutable scene proposal with reproducible proposal identity. + + Args: + parent_scene_id: Content/revision identity of the original scene. + seed: Nonnegative proposal-generation seed. + ordinal: Nonnegative position in the candidate source. + pose_changes: Unique existing entities and their proposed arena poses. + metadata: Unique string key/value provenance entries. Entries and pose + changes are sorted so equivalent input ordering has one identity. + + .. attention:: + ``variant_id`` identifies a proposal, not a settled initial state or a + successful execution. Hosts record those identities and evidence + separately. Metadata describes provenance, not executable operations. + """ + + parent_scene_id: str + seed: int + ordinal: int + pose_changes: tuple[ScenePoseChange, ...] + metadata: tuple[tuple[str, str], ...] = () + + def __post_init__(self) -> None: + _identifier(self.parent_scene_id, "parent_scene_id") + _nonnegative_integer(self.seed, "seed") + _nonnegative_integer(self.ordinal, "ordinal") + try: + changes = tuple(self.pose_changes) + except TypeError as exc: + raise ValueError( + "pose_changes must contain ScenePoseChange values" + ) from exc + if any(not isinstance(change, ScenePoseChange) for change in changes): + raise ValueError("pose_changes must contain ScenePoseChange values") + if len({change.entity_uid for change in changes}) != len(changes): + raise ValueError("pose_changes must contain unique entity UIDs") + object.__setattr__( + self, + "pose_changes", + tuple(sorted(changes, key=lambda change: change.entity_uid)), + ) + try: + entries = tuple(self.metadata) + if any(isinstance(entry, str) for entry in entries): + raise ValueError("metadata must contain string key/value pairs") + metadata = tuple(tuple(entry) for entry in entries) + except TypeError as exc: + raise ValueError("metadata must contain string key/value pairs") from exc + if any(len(entry) != 2 for entry in metadata): + raise ValueError("metadata must contain string key/value pairs") + for key, value in metadata: + _identifier(key, "metadata key") + if not isinstance(value, str): + raise ValueError("metadata values must be strings") + if len({key for key, _ in metadata}) != len(metadata): + raise ValueError("metadata must contain unique keys") + object.__setattr__(self, "metadata", tuple(sorted(metadata))) + + @property + def variant_id(self) -> str: + """Return a stable identity including geometry, source, seed, and ordinal. + + Returns: + SHA-256 identity independent of Python hash randomization and + temporary physical environment assignments. + """ + payload = { + "schema": "scene-variant/v1", + "parent_scene_id": self.parent_scene_id, + "seed": self.seed, + "ordinal": self.ordinal, + "pose_changes": tuple( + (change.entity_uid, change.arena_pose) for change in self.pose_changes + ), + "metadata": self.metadata, + } + encoded = json.dumps( + payload, sort_keys=True, separators=(",", ":"), allow_nan=False + ).encode("utf-8") + return "scene-variant:" + hashlib.sha256(encoded).hexdigest() diff --git a/embodichain/lab/sim/scene_expansion/source.py b/embodichain/lab/sim/scene_expansion/source.py new file mode 100644 index 000000000..09645dc9a --- /dev/null +++ b/embodichain/lab/sim/scene_expansion/source.py @@ -0,0 +1,107 @@ +# ---------------------------------------------------------------------------- +# Copyright (c) 2021-2026 DexForce Technology Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +"""Bounded fixed candidates for scene expansion hosts.""" + +from __future__ import annotations + +from collections.abc import Iterable, Iterator +from itertools import islice + +from .cfg import SceneExpansionCfg +from .contracts import ScenePoseChange, SceneVariant, _identifier + +__all__ = ["FixedSceneCandidateSource"] + + +class FixedSceneCandidateSource: + """Snapshot a bounded prefix of authored proposals for repeatable iteration. + + Args: + cfg: Proposal budget and allowed physical entity UIDs. + parent_scene_id: Identity of the scene the proposals modify. + candidates: Iterable of pose-change iterables. At most + ``cfg.max_candidates`` entries are consumed; later entries are + deliberately untouched. An empty pose-change iterable describes + an explicit nominal proposal. + metadata: Immutable string key/value provenance added to each proposal. + + The source owns no simulator, retries, or execution state. Configuration + is revalidated at this boundary and no mutable caller configuration is + retained. Every fresh iteration returns the same immutable proposals. + """ + + def __init__( + self, + cfg: SceneExpansionCfg, + parent_scene_id: str, + candidates: Iterable[Iterable[ScenePoseChange]], + *, + metadata: tuple[tuple[str, str], ...] = (), + ) -> None: + if not isinstance(cfg, SceneExpansionCfg): + raise TypeError("cfg must be a SceneExpansionCfg") + cfg.validate() + validated = cfg.copy() + _identifier(parent_scene_id, "parent_scene_id") + # Validate source metadata even if no candidates are available. + source_metadata = SceneVariant( + parent_scene_id, validated.seed, 0, (), metadata + ).metadata + permitted = set(validated.movable_entity_ids) + variants = [] + for ordinal, changes in enumerate(islice(candidates, validated.max_candidates)): + variant = SceneVariant( + parent_scene_id, + validated.seed, + ordinal, + tuple(changes), + source_metadata, + ) + unsupported = { + change.entity_uid for change in variant.pose_changes + } - permitted + if unsupported: + raise ValueError( + f"candidate changes entities not declared movable: {sorted(unsupported)}" + ) + variants.append(variant) + self._variants = tuple(variants) + + @property + def variants(self) -> tuple[SceneVariant, ...]: + """Return the bounded, immutable snapshot of candidate proposals. + + Returns: + Candidates in source order, with consecutive zero-based ordinals. + """ + return self._variants + + def __iter__(self) -> Iterator[SceneVariant]: + """Return a fresh iterator over the candidate snapshot. + + Returns: + Iterator independent of previous iterations or caller mutations. + """ + return iter(self._variants) + + def __len__(self) -> int: + """Return the number of snapshotted candidates. + + Returns: + Candidate count, bounded by the source configuration. + """ + return len(self._variants) diff --git a/embodichain/lab/task_program/integrations/scene_expansion.py b/embodichain/lab/task_program/integrations/scene_expansion.py new file mode 100644 index 000000000..3da0d87eb --- /dev/null +++ b/embodichain/lab/task_program/integrations/scene_expansion.py @@ -0,0 +1,314 @@ +# ---------------------------------------------------------------------------- +# Copyright (c) 2021-2026 DexForce Technology Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +"""Connect scene candidates to the existing Task Program demo executor.""" + +from __future__ import annotations + +from collections.abc import Callable, Mapping +from dataclasses import dataclass, field +from types import MappingProxyType +from typing import TYPE_CHECKING + +from embodichain.lab.gym.envs._json import json_safe_copy +from embodichain.lab.gym.envs.demo import ( + DemoEpisodeResult, + DemoExecutionCfg, + execute_demo_episode, +) +from embodichain.lab.sim.motion.expansion import ValidationResult +from embodichain.lab.sim.scene_expansion import SceneVariant +from embodichain.lab.task_program import CompiledTaskProgram, TaskProgramCfg + +if TYPE_CHECKING: + from embodichain.lab.gym.envs.embodied_env import EmbodiedEnv + +__all__ = [ + "ScenePreparationResult", + "SceneVariantEpisodeResult", + "execute_scene_variant", +] + + +def _validation_metadata( + validation: ValidationResult | None, +) -> list[dict[str, object]]: + if validation is None: + return [] + return [ + { + "check_id": check.check_id, + "status": check.status, + "detail": check.detail, + "metrics": dict(check.metrics), + } + for check in validation.checks + ] + + +def _freeze_metadata(value: object) -> object: + """Recursively freeze already validated and owned JSON-compatible data.""" + if isinstance(value, Mapping): + return MappingProxyType( + {key: _freeze_metadata(item) for key, item in value.items()} + ) + if isinstance(value, list): + return tuple(_freeze_metadata(item) for item in value) + return value + + +@dataclass(frozen=True) +class ScenePreparationResult: + """Host-owned settled state identity and explicit initial-state checks. + + Args: + initial_state_id: Identity of the actual state saved by the host after + preparation and settling. This is distinct from the proposal ID. + validation: Required initial-state checks based on the actual scene. + Empty or unavailable checks reject the candidate. + metadata: JSON-compatible provenance such as scene/integration versions + and the identity of the host's saved initial-state artifact. + """ + + initial_state_id: str + validation: ValidationResult + metadata: Mapping[str, object] = field(default_factory=dict) + + def __post_init__(self) -> None: + if ( + type(self.initial_state_id) is not str + or not self.initial_state_id + or self.initial_state_id.strip() != self.initial_state_id + ): + raise ValueError("initial_state_id must be a nonempty stripped string") + if not isinstance(self.validation, ValidationResult): + raise TypeError("validation must be a ValidationResult") + if not isinstance(self.metadata, Mapping): + raise TypeError("metadata must be a mapping") + owned = json_safe_copy(self.metadata, field_name="preparation.metadata") + object.__setattr__(self, "metadata", _freeze_metadata(owned)) + + +@dataclass(frozen=True) +class SceneVariantEpisodeResult: + """Independent scene preparation, program execution, and measured outcome. + + Args: + variant: Exact proposed scene evaluated by this attempt. + preparation: Actual settled initial-state identity and checks. + execution: Existing demo executor result, absent for an invalid initial + scene. Program completion alone does not imply acceptance. + measured_validation: Required measured task checks after execution, + absent when initial-state validation prevented execution. + """ + + variant: SceneVariant + preparation: ScenePreparationResult + execution: DemoEpisodeResult | None + measured_validation: ValidationResult | None + + def __post_init__(self) -> None: + if not isinstance(self.variant, SceneVariant): + raise TypeError("variant must be a SceneVariant") + if not isinstance(self.preparation, ScenePreparationResult): + raise TypeError("preparation must be a ScenePreparationResult") + if self.execution is not None and not isinstance( + self.execution, DemoEpisodeResult + ): + raise TypeError("execution must be a DemoEpisodeResult or None") + if self.measured_validation is not None and not isinstance( + self.measured_validation, ValidationResult + ): + raise TypeError("measured_validation must be a ValidationResult or None") + + @property + def accepted(self) -> bool: + """Return whether initial checks, execution, and measured checks passed. + + Returns: + Acceptance for this scene and attempt only; persistence is owned + by the existing caller and is not implied by this result. + """ + return ( + self.preparation.validation.accepted + and self.execution is not None + and self.execution.completed + and self.execution.all_success + and self.measured_validation is not None + and self.measured_validation.accepted + ) + + def to_metadata(self) -> dict[str, object]: + """Export JSON-compatible proposal, initial-state, and outcome evidence. + + Returns: + Provenance suitable for the existing episode recording path. + """ + return { + "schema_version": 1, + "variant": _variant_metadata(self.variant), + "initial_state_id": self.preparation.initial_state_id, + "preparation": json_safe_copy( + self.preparation.metadata, field_name="preparation.metadata" + ), + "initial_validation": _validation_metadata(self.preparation.validation), + "program_success": ( + self.execution is not None + and self.execution.completed + and self.execution.all_success + ), + "measured_validation": _validation_metadata(self.measured_validation), + "accepted": self.accepted, + } + + +def _variant_metadata(variant: SceneVariant) -> dict[str, object]: + return { + "variant_id": variant.variant_id, + "parent_scene_id": variant.parent_scene_id, + "seed": variant.seed, + "ordinal": variant.ordinal, + "pose_changes": [ + { + "entity_uid": change.entity_uid, + "arena_pose": [list(row) for row in change.arena_pose], + } + for change in variant.pose_changes + ], + "metadata": dict(variant.metadata), + } + + +def execute_scene_variant( + env: EmbodiedEnv, + variant: SceneVariant, + *, + prepare_scene: Callable[[SceneVariant], ScenePreparationResult], + measured_validator: Callable[[], ValidationResult], + task_program: TaskProgramCfg | CompiledTaskProgram | None = None, + episode_index: int = 0, + attempt_id: int = 0, + execution_cfg: DemoExecutionCfg | None = None, +) -> SceneVariantEpisodeResult: + """Prepare and evaluate one scene through the ordinary demo execution path. + + The host prepares/restores the candidate, settles physics, validates the + actual initial state, and refreshes recording observations and bindings. + Only then does the canonical + ``EmbodiedEnv.create_demo_segments(task_program=...)`` create the fresh + runtime bridge. Its segments flow through :func:`execute_demo_episode`, + including for subclasses with a handwritten segment-planner override. + + This function performs one attempt and no persistence. The caller retains + the existing transaction boundary: reset to commit an accepted episode or + reset with ``save_data=False`` to discard it. Failed or unverified scene + acceptance also blocks a later ordinary reset from saving this recording. + + Args: + env: Initialized single-row EmbodiedEnv or a wrapper around one. + variant: Existing-entity scene candidate to evaluate. + prepare_scene: Narrow host callback that applies/restores the variant, + settles and refreshes observations, and returns explicit checks. + measured_validator: Callback reading physical state and required + execution evidence. It must return ValidationResult, independently + of Task Program completion; arbitrary truthy values are rejected. + task_program: Explicit config/compiled program, or the environment's + configured Task Program. Handwritten action-list execution is not + accepted by this entry point. + episode_index: Existing demo episode metadata index. + attempt_id: Existing demo execution attempt index. + execution_cfg: Existing demo persistence layout settings. + + Returns: + Separate initial-state, program, and measured validation results with + an acceptance decision. Invalid initial checks prevent execution. + + Raises: + TypeError: For invalid contracts, callbacks, or program types. + ValueError: For unsupported batching or invalid attempt identifiers. + Exception: Host, planning, execution, and measurement errors propagate + after the episode is marked ineligible for persistence. + """ + from embodichain.lab.gym.envs.embodied_env import EmbodiedEnv + + target = getattr(env, "unwrapped", env) + if not isinstance(target, EmbodiedEnv): + raise TypeError("env must be an EmbodiedEnv or a wrapper around one") + if target.num_envs != 1: + raise ValueError("scene expansion currently requires B=1") + if not isinstance(variant, SceneVariant): + raise TypeError("variant must be a SceneVariant") + if not callable(prepare_scene) or not callable(measured_validator): + raise TypeError("prepare_scene and measured_validator must be callable") + selected_program = task_program + if selected_program is None: + selected_program = getattr(target.cfg, "task_program", None) + if type(selected_program) not in (TaskProgramCfg, CompiledTaskProgram): + raise TypeError( + "scene expansion requires a Task Program config or compiled program" + ) + for name, value in (("episode_index", episode_index), ("attempt_id", attempt_id)): + if type(value) is not int or value < 0: + raise ValueError(f"{name} must be a nonnegative integer") + if execution_cfg is not None and not isinstance(execution_cfg, DemoExecutionCfg): + raise TypeError("execution_cfg must be a DemoExecutionCfg or None") + + pending: dict[str, object] = { + "schema_version": 1, + "variant": _variant_metadata(variant), + "accepted": False, + "status": "preparing", + } + + def publish(metadata: Mapping[str, object]) -> None: + target._demo_episode_metadata[0]["scene_expansion"] = json_safe_copy( + metadata, field_name="scene_expansion" + ) + + publish(pending) + try: + preparation = prepare_scene(variant) + if not isinstance(preparation, ScenePreparationResult): + raise TypeError("prepare_scene must return a ScenePreparationResult") + if not preparation.validation.accepted: + result = SceneVariantEpisodeResult(variant, preparation, None, None) + publish(result.to_metadata()) + return result + + pending["initial_state_id"] = preparation.initial_state_id + pending["status"] = "executing" + publish(pending) + segments = EmbodiedEnv.create_demo_segments( + target, task_program=selected_program + ) + execution = execute_demo_episode( + env, + episode_index=episode_index, + attempt_id=attempt_id, + execution_cfg=execution_cfg, + segments=() if segments is None else segments, + ) + measured = measured_validator() + if not isinstance(measured, ValidationResult): + raise TypeError("measured_validator must return a ValidationResult") + result = SceneVariantEpisodeResult(variant, preparation, execution, measured) + publish(result.to_metadata()) + return result + except BaseException as error: + pending["status"] = "error" + pending["error_type"] = type(error).__name__ + publish(pending) + raise diff --git a/tests/gym/envs/task_program/test_scene_expansion.py b/tests/gym/envs/task_program/test_scene_expansion.py new file mode 100644 index 000000000..974d56611 --- /dev/null +++ b/tests/gym/envs/task_program/test_scene_expansion.py @@ -0,0 +1,639 @@ +# ---------------------------------------------------------------------------- +# Copyright (c) 2021-2026 DexForce Technology Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +"""Scene candidate integration through real demo and recording boundaries.""" + +from __future__ import annotations + +from collections.abc import Iterator +from contextlib import nullcontext +from types import SimpleNamespace +from typing import Any + +import pytest +import torch + +from embodichain.lab.gym.envs.demo import ( + DemoExecutionCfg, + DemoSegment, + execute_demo_episode, +) +from embodichain.lab.gym.envs.embodied_env import EmbodiedEnv +from embodichain.lab.gym.envs.managers.record import record_camera_data +from embodichain.lab.gym.envs.types import ControllerAction +from embodichain.lab.sim.motion.expansion import ValidationCheck, ValidationResult +from embodichain.lab.sim.scene_expansion import ScenePoseChange, SceneVariant +from embodichain.lab.task_program import ( + CompiledTaskProgram, + InvokeCfg, + PickCfg, + TaskProgramCfg, + TaskProgramIntegrationCfg, +) +from embodichain.lab.task_program.integrations.scene_expansion import ( + ScenePreparationResult, + execute_scene_variant, +) + +_GOAL_X = 0.5 +_START_X = 0.2 + + +def _program() -> TaskProgramCfg: + return TaskProgramCfg( + program_id="scene-expansion-fixture", + integration=TaskProgramIntegrationCfg( + robot_profile="fixture-robot", + scene_registry="fixture-scene", + runtime_preset="fixture-runtime", + ), + program=InvokeCfg(call=PickCfg(object="cube")), + ) + + +def _checks(status: str = "passed") -> ValidationResult: + return ValidationResult((ValidationCheck("measured-object-position", status),)) + + +def _variant() -> SceneVariant: + pose = torch.eye(4).tolist() + pose[0][3] = _START_X + return SceneVariant( + parent_scene_id="fixture-scene-v1", + seed=7, + ordinal=0, + pose_changes=(ScenePoseChange("cube", pose),), + metadata=(("source", "fixed-fixture"),), + ) + + +class _CameraRecorder(record_camera_data): + """Exercise the production camera save/discard routing without IO.""" + + def __init__(self) -> None: + self.saved = 0 + self.discarded = 0 + + def save_and_clear(self, env_ids: torch.Tensor | None = None) -> None: + self.saved += 1 + + def discard_and_clear(self, env_ids: torch.Tensor | None = None) -> None: + self.discarded += 1 + + +class _DatasetRecorder: + """Capture the real buffered metadata when reset requests a save.""" + + available_modes = ("save",) + save_failed_episodes = True + + def __init__(self, env: _CandidateEnv) -> None: + self.env = env + self.saved: list[dict[str, Any]] = [] + + def apply(self, *, mode: str, env_ids: torch.Tensor) -> None: + assert mode == "save" + for env_id in env_ids.tolist(): + self.saved.append(self.env.get_demo_episode_metadata(env_id)) + + def reset(self, *, env_ids: Any) -> None: + pass + + +class _Bridge: + """A deterministic trusted runtime producing one controller command.""" + + expansion_records = () + + def __init__(self, *, runtime_passes: bool) -> None: + self.program_completed = False + self.completion_mask = torch.tensor([runtime_passes]) + + def iter_segments(self) -> Iterator[DemoSegment]: + yield DemoSegment( + actions=(ControllerAction(value=torch.tensor([[_GOAL_X]])),), + name="move-cube", + target_uid="cube", + validator=lambda: self.completion_mask, + ) + self.program_completed = True + + +class _CandidateEnv(EmbodiedEnv): + """Use production bridge selection, demo execution, metadata, and reset IO. + + Only physics and runtime planning are replaced by deterministic CPU ports. + This lets the tests distinguish program completion from measured state and + exercise the real delayed persistence boundary. + """ + + def __init__(self) -> None: + self._num_envs = 1 + self.sim = SimpleNamespace(device=torch.device("cpu")) + self.cfg = SimpleNamespace( + task_program=_program(), + seed=7, + events=True, + observations=None, + rewards=None, + dataset=True, + trajectory_auto_save=True, + ) + self._profiler = SimpleNamespace(section=lambda *args, **kwargs: nullcontext()) + self._demo_steps = torch.zeros(1, dtype=torch.long) + self.rollout_steps = torch.zeros(1, dtype=torch.long) + self.rollout_buffer = None + self._demo_active_segment_ids = torch.zeros(1, dtype=torch.long) + self._demo_active_mask = torch.ones(1, dtype=torch.bool) + self._demo_segment_participants = torch.zeros(1, dtype=torch.bool) + self._demo_active_segment_start_steps = self._demo_steps.clone() + self._demo_active_rollout_start_steps = self.rollout_steps.clone() + self._demo_episode_metadata = [self._new_demo_episode_metadata(0)] + self._active_task_program_bridge = None + self._demo_no_auto_reset = False + self.episode_success_status = torch.zeros(1, dtype=torch.bool) + self._task_success = torch.zeros(1, dtype=torch.bool) + self.action_manager = None + self.camera_recorder = _CameraRecorder() + self.event_manager = SimpleNamespace( + _mode_functor_cfgs={"record": [SimpleNamespace(func=self.camera_recorder)]}, + available_modes=(), + reset=lambda **kwargs: None, + ) + self.dataset_manager = _DatasetRecorder(self) + self._traj_buffer = object() + self._traj_steps = torch.zeros(1, dtype=torch.long) + self.trajectory_saves = 0 + self.object_x = 0.0 + self.first_observations: list[float] = [] + self.bridge_initial_positions: list[float] = [] + self.runtime_passes = True + self.physics_moves = True + self.fail_step = False + + def compile_task_program(self, program: TaskProgramCfg) -> CompiledTaskProgram: + return object.__new__(CompiledTaskProgram) + + def create_task_program_bridge(self, program: CompiledTaskProgram) -> _Bridge: + self.bridge_initial_positions.append(self.object_x) + return _Bridge(runtime_passes=self.runtime_passes) + + def step(self, action: ControllerAction) -> tuple[Any, ...]: + assert self._demo_no_auto_reset + self.first_observations.append(self.object_x) + if self.fail_step: + raise RuntimeError("physics port failed") + if self.physics_moves: + self.object_x = float(action.value[0, 0]) + self._demo_steps += 1 + self.rollout_steps += 1 + self._traj_steps += 1 + return ( + None, + torch.zeros(1), + torch.zeros(1, dtype=torch.bool), + torch.zeros(1, dtype=torch.bool), + {}, + ) + + def _save_trajectory_for_env(self, env_id: int) -> str: + self.trajectory_saves += 1 + return "fixture.pt" + + def prepare(self, variant: SceneVariant) -> ScenePreparationResult: + self._initialize_episode(save_data=False) + self._active_task_program_bridge = None + self.object_x = variant.pose_changes[0].arena_pose[0][3] + return ScenePreparationResult( + initial_state_id=f"settled-cube-{self.object_x}", + validation=_checks(), + metadata={"scene_revision": "revision-1"}, + ) + + def measure(self) -> ValidationResult: + status = "passed" if abs(self.object_x - _GOAL_X) < 1e-6 else "failed" + return _checks(status) + + def persist_via_reset(self, **kwargs: Any) -> None: + # BaseEnv.reset computes task success before _initialize_episode. + self._task_success = self.is_task_success() + self._initialize_episode(**kwargs) + self._active_task_program_bridge = None + + +def test_fixed_candidate_uses_fresh_bridge_and_actual_recording_metadata() -> None: + env = _CandidateEnv() + variant = _variant() + + result = execute_scene_variant( + env, + variant, + prepare_scene=env.prepare, + measured_validator=env.measure, + episode_index=3, + attempt_id=2, + ) + + assert result.accepted + assert result.execution.length == 1 + assert result.execution.episode_index == 3 + assert result.execution.attempt_id == 2 + assert env.bridge_initial_positions == [_START_X] + assert env.first_observations == [_START_X] + assert env.dataset_manager.saved == [] + provenance = env.get_demo_episode_metadata(0)["scene_expansion"] + assert provenance["variant"]["variant_id"] == variant.variant_id + assert provenance["variant"]["parent_scene_id"] == variant.parent_scene_id + assert provenance["initial_state_id"] == result.preparation.initial_state_id + assert provenance["initial_state_id"] != variant.variant_id + assert provenance["measured_validation"][0]["status"] == "passed" + + env.persist_via_reset() + + assert len(env.dataset_manager.saved) == 1 + assert env.dataset_manager.saved[0]["scene_expansion"] == provenance + assert env.camera_recorder.saved == 1 + assert env.trajectory_saves == 1 + + +@pytest.mark.parametrize("mode", ("continuous", "segment_fragments")) +def test_physical_failure_blocks_all_later_reset_persistence(mode: str) -> None: + env = _CandidateEnv() + env.physics_moves = False + + result = execute_scene_variant( + env, + _variant(), + prepare_scene=env.prepare, + measured_validator=env.measure, + execution_cfg=DemoExecutionCfg( + mode=mode, save_failed_fragments=(mode == "segment_fragments") + ), + ) + + assert result.execution.all_success + assert result.execution.completed + assert not result.measured_validation.accepted + assert not result.accepted + assert env.is_task_success().tolist() == [True] + + env.persist_via_reset(save_data=True, commit_env_ids=[0]) + + assert env.dataset_manager.saved == [] + assert env.camera_recorder.saved == 0 + assert env.camera_recorder.discarded == 2 # Preparation and rejected attempt. + assert env.trajectory_saves == 0 + + +@pytest.mark.parametrize("status", ("failed", "unavailable", "not_run")) +def test_invalid_initial_scene_prevents_program_execution(status: str) -> None: + env = _CandidateEnv() + + def prepare(variant: SceneVariant) -> ScenePreparationResult: + prepared = env.prepare(variant) + return ScenePreparationResult(prepared.initial_state_id, _checks(status)) + + def must_not_measure() -> ValidationResult: + raise AssertionError("an invalid initial scene must not execute") + + result = execute_scene_variant( + env, + _variant(), + prepare_scene=prepare, + measured_validator=must_not_measure, + ) + + assert not result.accepted + assert result.execution is None + assert env.bridge_initial_positions == [] + assert env.first_observations == [] + env.persist_via_reset() + assert env.dataset_manager.saved == [] + + +@pytest.mark.parametrize( + "measured", + (ValidationResult(()), _checks("unavailable"), _checks("not_run")), +) +def test_missing_measured_evidence_cannot_accept_program_success( + measured: ValidationResult, +) -> None: + env = _CandidateEnv() + result = execute_scene_variant( + env, + _variant(), + prepare_scene=env.prepare, + measured_validator=lambda: measured, + ) + + assert result.execution.all_success + assert not result.accepted + env.persist_via_reset() + assert env.dataset_manager.saved == [] + + +@pytest.mark.parametrize("value", (True, "passed", {"accepted": True})) +def test_validator_rejects_arbitrary_truthy_values_and_blocks_saving( + value: object, +) -> None: + env = _CandidateEnv() + + with pytest.raises(TypeError, match="measured_validator must return"): + execute_scene_variant( + env, + _variant(), + prepare_scene=env.prepare, + measured_validator=lambda: value, + ) + + assert env.get_demo_episode_metadata(0)["scene_expansion"]["accepted"] is False + env.persist_via_reset() + assert env.dataset_manager.saved == [] + assert env.camera_recorder.saved == 0 + assert env.trajectory_saves == 0 + + +def test_measurement_exception_marks_completed_program_ineligible_for_saving() -> None: + env = _CandidateEnv() + + def fail_measurement() -> ValidationResult: + raise RuntimeError("physical evidence unavailable") + + with pytest.raises(RuntimeError, match="physical evidence unavailable"): + execute_scene_variant( + env, + _variant(), + prepare_scene=env.prepare, + measured_validator=fail_measurement, + ) + + assert env.is_task_success().tolist() == [True] + assert ( + env.get_demo_episode_metadata(0)["scene_expansion"]["error_type"] + == "RuntimeError" + ) + env.persist_via_reset(commit_env_ids=[0]) + assert env.dataset_manager.saved == [] + assert env.trajectory_saves == 0 + + +def test_preparation_failure_does_not_leave_the_previous_episode_saveable() -> None: + env = _CandidateEnv() + env.episode_success_status.fill_(True) + + def fail_preparation(variant: SceneVariant) -> ScenePreparationResult: + raise RuntimeError("scene restore failed") + + with pytest.raises(RuntimeError, match="scene restore failed"): + execute_scene_variant( + env, + _variant(), + prepare_scene=fail_preparation, + measured_validator=env.measure, + ) + + env.persist_via_reset() + assert env.dataset_manager.saved == [] + assert env.camera_recorder.saved == 0 + + +def test_successful_measurements_cannot_override_program_failure() -> None: + env = _CandidateEnv() + env.runtime_passes = False + + result = execute_scene_variant( + env, + _variant(), + prepare_scene=env.prepare, + measured_validator=env.measure, + ) + + assert result.measured_validation.accepted + assert not result.execution.all_success + assert not result.accepted + env.persist_via_reset() + assert env.dataset_manager.saved == [] + + +def test_execution_error_fails_closed_after_begin_recording_replaced_metadata() -> None: + env = _CandidateEnv() + env.fail_step = True + + with pytest.raises(RuntimeError, match="emergency safe-stop attempt") as error: + execute_scene_variant( + env, + _variant(), + prepare_scene=env.prepare, + measured_validator=env.measure, + ) + + assert str(error.value.__cause__) == "physics port failed" + assert env.get_demo_episode_metadata(0)["scene_expansion"]["accepted"] is False + env.persist_via_reset(commit_env_ids=[0]) + assert env.dataset_manager.saved == [] + assert env.camera_recorder.saved == 0 + + +def test_batch_and_legacy_expert_inputs_fail_before_scene_preparation() -> None: + env = _CandidateEnv() + env._num_envs = 2 + with pytest.raises(ValueError, match="B=1"): + execute_scene_variant( + env, _variant(), prepare_scene=env.prepare, measured_validator=env.measure + ) + + env._num_envs = 1 + env.cfg.task_program = None + with pytest.raises(TypeError, match="requires a Task Program"): + execute_scene_variant( + env, _variant(), prepare_scene=env.prepare, measured_validator=env.measure + ) + assert env.bridge_initial_positions == [] + + +def test_episode_program_override_uses_the_same_task_program_path() -> None: + env = _CandidateEnv() + env.cfg.task_program = None + + result = execute_scene_variant( + env, + _variant(), + prepare_scene=env.prepare, + measured_validator=env.measure, + task_program=_program(), + ) + + assert result.accepted + assert len(env.bridge_initial_positions) == 1 + + +def test_normal_demo_recording_is_unchanged_without_scene_expansion() -> None: + env = _CandidateEnv() + result = execute_demo_episode(env) + + assert result.all_success + assert "scene_expansion" not in env.get_demo_episode_metadata(0) + env.persist_via_reset() + assert len(env.dataset_manager.saved) == 1 + assert env.camera_recorder.saved == 1 + assert env.trajectory_saves == 1 + + +def test_rejected_scene_does_not_poison_the_next_fresh_attempt() -> None: + env = _CandidateEnv() + env.physics_moves = False + rejected = execute_scene_variant( + env, + _variant(), + prepare_scene=env.prepare, + measured_validator=env.measure, + ) + env.persist_via_reset() + env.physics_moves = True + accepted = execute_scene_variant( + env, + _variant(), + prepare_scene=env.prepare, + measured_validator=env.measure, + attempt_id=1, + ) + env.persist_via_reset() + + assert not rejected.accepted + assert accepted.accepted + assert env.bridge_initial_positions == [_START_X, _START_X] + assert len(env.dataset_manager.saved) == 1 + assert env.dataset_manager.saved[0]["attempt_id"] == 1 + assert env.dataset_manager.saved[0]["scene_expansion"]["accepted"] is True + + +def test_preparation_callback_requires_an_explicit_validation_contract() -> None: + env = _CandidateEnv() + + with pytest.raises(TypeError, match="prepare_scene must return"): + execute_scene_variant( + env, + _variant(), + prepare_scene=lambda variant: True, + measured_validator=env.measure, + ) + + assert env.bridge_initial_positions == [] + env.persist_via_reset(commit_env_ids=[0]) + assert env.dataset_manager.saved == [] + + +class _LegacySegmentOverrideEnv(_CandidateEnv): + """A legacy task override must not replace the selected Task Program.""" + + def __init__(self) -> None: + super().__init__() + self.legacy_calls = 0 + + def create_demo_segments(self, **kwargs: Any) -> tuple[DemoSegment, ...]: + self.legacy_calls += 1 + return ( + DemoSegment( + actions=(ControllerAction(value=torch.tensor([[_GOAL_X]])),), + name="legacy-expert", + ), + ) + + def is_task_success(self, **kwargs: Any) -> torch.Tensor: + return torch.ones(1, dtype=torch.bool) + + +def test_scene_execution_cannot_be_replaced_by_a_legacy_segment_override() -> None: + env = _LegacySegmentOverrideEnv() + + result = execute_scene_variant( + env, + _variant(), + prepare_scene=env.prepare, + measured_validator=env.measure, + ) + + assert result.accepted + assert env.legacy_calls == 0 + assert env.bridge_initial_positions == [_START_X] + assert result.execution.segments[0].name == "move-cube" + + +def test_explicit_demo_segments_keep_instruction_fallback_without_replanning() -> None: + env = _LegacySegmentOverrideEnv() + env.metadata = {"dataset": {"instruction": {"lang": "Move the cube"}}} + + result = execute_demo_episode( + env, + segments=( + DemoSegment( + actions=(ControllerAction(value=torch.tensor([[_GOAL_X]])),), + name="explicit", + ), + ), + ) + + assert result.all_success + assert env.legacy_calls == 0 + assert result.segments[0].instruction == "Move the cube" + assert ( + env.get_demo_episode_metadata(0)["segments"][0]["instruction"] + == "Move the cube" + ) + + +def test_explicit_demo_segments_require_the_normal_segment_contract() -> None: + env = _LegacySegmentOverrideEnv() + + with pytest.raises(TypeError, match="must yield DemoSegment"): + execute_demo_episode(env, segments=(object(),)) + + assert env.legacy_calls == 0 + assert env.first_observations == [] + + +def test_empty_explicit_demo_segments_do_not_fall_back_to_a_legacy_planner() -> None: + env = _LegacySegmentOverrideEnv() + + result = execute_demo_episode(env, segments=()) + + assert not result.all_success + assert result.terminal_reason == "empty_plan" + assert env.legacy_calls == 0 + + +def test_preparation_metadata_owns_and_freezes_nested_provenance() -> None: + source = {"nested": {"ids": ["snapshot-v1"]}} + prepared = ScenePreparationResult("initial-state-v1", _checks(), source) + source["nested"]["ids"][0] = "mutated-source" + + assert prepared.metadata["nested"]["ids"] == ("snapshot-v1",) + with pytest.raises(TypeError): + prepared.metadata["nested"]["ids"][0] = "mutated-preparation" + with pytest.raises(TypeError): + prepared.metadata["nested"]["ids"] = () + + env = _CandidateEnv() + result = execute_scene_variant( + env, + _variant(), + prepare_scene=lambda variant: prepared, + measured_validator=env.measure, + ) + exported = result.to_metadata() + exported["preparation"]["nested"]["ids"][0] = "mutated-export" + assert prepared.metadata["nested"]["ids"] == ("snapshot-v1",) + assert result.to_metadata()["preparation"]["nested"]["ids"] == ["snapshot-v1"] diff --git a/tests/sim/scene_expansion/test_contracts.py b/tests/sim/scene_expansion/test_contracts.py new file mode 100644 index 000000000..085e333b0 --- /dev/null +++ b/tests/sim/scene_expansion/test_contracts.py @@ -0,0 +1,145 @@ +# ---------------------------------------------------------------------------- +# Copyright (c) 2021-2026 DexForce Technology Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +from __future__ import annotations + +from dataclasses import FrozenInstanceError, replace +import math + +import pytest + +from embodichain.lab.sim.scene_expansion import ScenePoseChange, SceneVariant + + +def _pose(x: float = 0.0) -> list[list[float]]: + # Non-symmetric yaw detects row/column transposition and reflections. + angle = math.pi / 3 + return [ + [math.cos(angle), -math.sin(angle), 0.0, x], + [math.sin(angle), math.cos(angle), 0.0, 0.2], + [0.0, 0.0, 1.0, 0.5], + [0.0, 0.0, 0.0, 1.0], + ] + + +def _variant(**changes: object) -> SceneVariant: + fields = dict( + parent_scene_id="scene:revision-1", + seed=7, + ordinal=0, + pose_changes=(ScenePoseChange("cube", _pose()),), + metadata=(("source", "handwritten"),), + ) + fields.update(changes) + return SceneVariant(**fields) + + +def test_pose_is_copied_into_immutable_arena_frame_matrix() -> None: + matrix = _pose(0.3) + change = ScenePoseChange("cube", matrix) + matrix[0][3] = 9.0 + + assert change.arena_pose[0][3] == pytest.approx(0.3) + assert change.arena_pose[0][1] == pytest.approx(-math.sin(math.pi / 3)) + with pytest.raises(TypeError): + change.arena_pose[0][3] = 2.0 + with pytest.raises(FrozenInstanceError): + change.entity_uid = "different" + + +@pytest.mark.parametrize( + "matrix", + [ + [[1.0] * 3 for _ in range(3)], + [[0.0] * 4 for _ in range(4)], + [[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], + [[2, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], + [[1, 0.2, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], + [[1, 0, 0, math.inf], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], + [[1, 0, 0, math.nan], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], + [[True, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], + [[1, 0, 0, "0"], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], + [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0.1, 0, 0, 1]], + ], +) +def test_pose_rejects_nonrigid_or_nonfinite_matrices(matrix: object) -> None: + with pytest.raises(ValueError, match="arena_pose"): + ScenePoseChange("cube", matrix) + + +@pytest.mark.parametrize("uid", ["", " cube", "cube ", None]) +def test_pose_requires_stable_nonempty_entity_uid(uid: object) -> None: + with pytest.raises(ValueError, match="entity_uid"): + ScenePoseChange(uid, _pose()) + + +def test_variant_identity_is_canonical_and_metadata_is_detached() -> None: + changes = [ScenePoseChange("z", _pose()), ScenePoseChange("a", _pose(0.4))] + metadata = [["revision", "v1"], ["source", "fixed"]] + first = _variant(pose_changes=changes, metadata=metadata) + equivalent = _variant(pose_changes=reversed(changes), metadata=reversed(metadata)) + changes.clear() + metadata[0][1] = "changed" + + assert first.variant_id == equivalent.variant_id + assert tuple(change.entity_uid for change in first.pose_changes) == ("a", "z") + assert first.metadata == (("revision", "v1"), ("source", "fixed")) + with pytest.raises(FrozenInstanceError): + first.ordinal = 4 + + +@pytest.mark.parametrize( + "changes", + [ + {"parent_scene_id": "scene:revision-2"}, + {"seed": 8}, + {"ordinal": 1}, + {"pose_changes": (ScenePoseChange("cube", _pose(0.1)),)}, + {"metadata": (("source", "generated"),)}, + ], +) +def test_variant_identity_captures_proposal_inputs(changes: dict[str, object]) -> None: + original = _variant() + assert replace(original, **changes).variant_id != original.variant_id + + +@pytest.mark.parametrize( + "changes", + [ + {"parent_scene_id": ""}, + {"seed": True}, + {"seed": -1}, + {"ordinal": -1}, + {"ordinal": 0.5}, + {"pose_changes": ("cube",)}, + {"pose_changes": (ScenePoseChange("cube", _pose()),) * 2}, + {"metadata": (("source", "a"), ("source", "b"))}, + {"metadata": (("source", []),)}, + {"metadata": ("ab",)}, + {"metadata": (("source",),)}, + ], +) +def test_variant_rejects_ambiguous_or_mutable_inputs( + changes: dict[str, object], +) -> None: + with pytest.raises(ValueError): + _variant(**changes) + + +def test_nominal_variant_has_explicit_proposal_identity() -> None: + nominal = _variant(pose_changes=()) + assert nominal.pose_changes == () + assert nominal.variant_id != _variant().variant_id diff --git a/tests/sim/scene_expansion/test_source.py b/tests/sim/scene_expansion/test_source.py new file mode 100644 index 000000000..a69752609 --- /dev/null +++ b/tests/sim/scene_expansion/test_source.py @@ -0,0 +1,153 @@ +# ---------------------------------------------------------------------------- +# Copyright (c) 2021-2026 DexForce Technology Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import MISSING + +import pytest + +from embodichain.lab.sim.scene_expansion import ( + FixedSceneCandidateSource, + SceneExpansionCfg, + ScenePoseChange, +) + + +def _change(uid: str = "cube", x: float = 0.0) -> ScenePoseChange: + return ScenePoseChange( + uid, ((1, 0, 0, x), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)) + ) + + +def test_fixed_source_consumes_only_budgeted_prefix_and_repeats_it() -> None: + consumed = [] + + def generate() -> Iterator[tuple[ScenePoseChange, ...]]: + for index in range(3): + consumed.append(index) + yield (_change(x=index / 10),) + + candidates = generate() + source = FixedSceneCandidateSource( + SceneExpansionCfg(seed=23, max_candidates=2, movable_entity_ids=("cube",)), + "scene:revision-1", + candidates, + ) + + assert consumed == [0, 1] + assert len(source) == 2 + assert [variant.ordinal for variant in source] == [0, 1] + assert {variant.seed for variant in source} == {23} + assert tuple(source) == tuple(source) == source.variants + assert next(candidates)[0].arena_pose[0][3] == pytest.approx(0.2) + + +def test_source_owns_config_and_candidate_containers() -> None: + movable = ["cube"] + cfg = SceneExpansionCfg(seed=5, max_candidates=2, movable_entity_ids=movable) + changes = [_change()] + candidates = [changes] + metadata = [["source", "fixed"]] + source = FixedSceneCandidateSource(cfg, "scene", candidates, metadata=metadata) + original_id = source.variants[0].variant_id + movable.append("other") + cfg.seed = 9 + cfg.max_candidates = 3 + cfg.movable_entity_ids = ("other",) + changes.clear() + candidates.clear() + metadata[0][1] = "mutated" + + assert source.variants[0].variant_id == original_id + assert source.variants[0].pose_changes == (_change(),) + assert source.variants[0].metadata == (("source", "fixed"),) + + +@pytest.mark.parametrize( + "kwargs", + [ + {"seed": -1}, + {"seed": True}, + {"seed": 0.5}, + {"max_candidates": 0}, + {"max_candidates": -1}, + {"max_candidates": True}, + {"movable_entity_ids": ("cube", "cube")}, + {"movable_entity_ids": ("",)}, + {"movable_entity_ids": "cube"}, + {"movable_entity_ids": None}, + ], +) +def test_config_rejects_invalid_budget_seed_and_permissions( + kwargs: dict[str, object], +) -> None: + with pytest.raises(ValueError): + SceneExpansionCfg(**kwargs) + + +def test_config_detaches_mutable_permission_list() -> None: + movable = ["cube"] + cfg = SceneExpansionCfg(movable_entity_ids=movable) + movable.append("target") + assert cfg.movable_entity_ids == ("cube",) + + +def test_source_revalidates_config_after_in_place_updates() -> None: + cfg = SceneExpansionCfg() + cfg.max_candidates = -1 + with pytest.raises(ValueError, match="max_candidates"): + FixedSceneCandidateSource(cfg, "scene", [()]) + + +def test_source_rejects_unresolved_configuration() -> None: + cfg = SceneExpansionCfg() + cfg.seed = MISSING + with pytest.raises(TypeError, match="seed"): + FixedSceneCandidateSource(cfg, "scene", [()]) + + +def test_source_rejects_changes_to_entities_not_declared_movable() -> None: + with pytest.raises(ValueError, match="not declared movable.*target"): + FixedSceneCandidateSource( + SceneExpansionCfg(movable_entity_ids=("cube",)), + "scene", + [(_change("target"),)], + ) + + +def test_source_rejects_duplicate_entity_changes() -> None: + with pytest.raises(ValueError, match="unique entity UIDs"): + FixedSceneCandidateSource( + SceneExpansionCfg(movable_entity_ids=("cube",)), + "scene", + [(_change(), _change(x=0.1))], + ) + + +def test_source_supports_empty_and_explicit_nominal_candidates() -> None: + assert not tuple(FixedSceneCandidateSource(SceneExpansionCfg(), "scene", [])) + nominal = FixedSceneCandidateSource(SceneExpansionCfg(), "scene", [()]) + assert len(nominal) == 1 + assert nominal.variants[0].pose_changes == () + + +def test_empty_source_still_rejects_invalid_provenance() -> None: + with pytest.raises(ValueError, match="metadata values"): + FixedSceneCandidateSource( + SceneExpansionCfg(), "scene", [], metadata=(("source", []),) + ) diff --git a/tests/test_agent_context_map.py b/tests/test_agent_context_map.py index 9a421cde1..9e6045137 100644 --- a/tests/test_agent_context_map.py +++ b/tests/test_agent_context_map.py @@ -69,6 +69,7 @@ def test_map_registers_the_supported_context_domains() -> None: "data-assets", "data-pipeline", "robot-workspace", + "scene-expansion", "differentiable-env", } From b3797cf7091ea55cc67b93a7d4f22488ed499fd5 Mon Sep 17 00:00:00 2001 From: yuecideng Date: Sat, 10 Oct 2026 14:56:17 +0000 Subject: [PATCH 2/4] docs(scene): include expansion in architecture coverage --- docs/architecture/README.md | 8 +- docs/architecture/curated.json | 125 +- docs/architecture/generated/architecture.json | 1229 +++++++++-------- docs/architecture/generated/summary.md | 232 ++-- 4 files changed, 926 insertions(+), 668 deletions(-) diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 8c95616d0..48c287030 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -21,10 +21,10 @@ ownership. This is selected static coverage, not a complete dependency graph. The generated text reference lists topics without represented nodes and overview nodes without mapped relationships, using MAP at the snapshot revision. Neither a represented topic nor a missing edge establishes complete coverage or independence. -The current snapshot represents all 18 MAP topics and all 10 top-level production -packages. Frontend data tests enforce these entry-point checks and ensure every -selected node appears in a view. Specialist modules are included in the searchable -text reference even when omitted from the overview. +The current snapshot represents every MAP topic and top-level production package +at its pinned source revision. Frontend data tests enforce these entry-point +checks and ensure every selected node appears in a view. Specialist modules are +included in the searchable text reference even when omitted from the overview. ## Generate and validate diff --git a/docs/architecture/curated.json b/docs/architecture/curated.json index 575b41527..31b431d17 100644 --- a/docs/architecture/curated.json +++ b/docs/architecture/curated.json @@ -1211,6 +1211,70 @@ } ] }, + { + "id": "scene-expansion", + "label": "Scene expansion contracts", + "kind": "package", + "topic_ids": [ + "scene-expansion" + ], + "summary": "Defines immutable arena-local scene proposals and bounded authored candidate sources.", + "details": "The scene expansion core represents existing-entity pose changes with stable proposal identity, movable-entity permissions, provenance, and a bounded fixed-candidate source. Task semantics, physical preparation, execution, and persistence remain host responsibilities.", + "boundaries": [ + "The core does not import Task Program, GenSim or Gym and does not reset, step or validate task success." + ], + "evidence": [ + { + "path": "embodichain/lab/sim/scene_expansion/__init__.py", + "symbol": "", + "start_line": 25, + "end_line": 27, + "excerpt": "from .cfg import SceneExpansionCfg\nfrom .contracts import ScenePoseChange, SceneVariant\nfrom .source import FixedSceneCandidateSource" + }, + { + "path": "embodichain/lab/sim/scene_expansion/source.py", + "symbol": "FixedSceneCandidateSource", + "start_line": 30, + "end_line": 30, + "excerpt": "class FixedSceneCandidateSource:" + } + ], + "documentation": [ + { + "docname": "api_reference/embodichain/embodichain.lab.sim.scene_expansion", + "label": "Scene expansion contracts and fixed candidates" + } + ] + }, + { + "id": "scene-expansion-integration", + "label": "Scene expansion attempt", + "kind": "function", + "topic_ids": [ + "scene-expansion", + "task-programs" + ], + "summary": "Prepares one scene candidate and executes a Task Program with separate measured acceptance.", + "details": "execute_scene_variant connects a scene proposal to injected preparation and measurement callbacks. For a valid initial state it creates the canonical Task Program segments and uses the existing Gym demo executor, recording separate initial-state, program, and measured-outcome evidence.", + "boundaries": [ + "One B=1 attempt; the caller supplies physical preparation, measured task evidence, retry policy, and the reset-time persistence transaction." + ], + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/scene_expansion.py", + "symbol": "execute_scene_variant", + "start_line": 195, + "end_line": 195, + "excerpt": "def execute_scene_variant(" + } + ], + "documentation": [ + { + "docname": "api_reference/embodichain/embodichain.lab.task_program.integrations.scene_expansion", + "label": "Task Program scene preparation and measured acceptance" + } + ] + }, { "id": "differentiable-env", "label": "DifferentiableEnv", @@ -2513,7 +2577,7 @@ "symbol": "", "start_line": 24, "end_line": 35, - "excerpt": "\"\"\"Depth quantization/dequantization for compressed depth sidecar videos.\n\nDepth maps are packed into 12-bit integer codes (``uint16``, values\n``0\u2026DEPTH_QMAX``) so they fit the ``gray12le`` pixel format and can be encoded\nlosslessly by HEVC (``libx265``). Logarithmic quantization is the default\nbecause it allocates more quanta to near-range depth, matching the\n``1/depth`` error profile of typical depth sensors.\n\nThe math is ported from lerobot 0.6.0 (itself adapted from BEHAVIOR-1K's\n``obs_utils.py``) and depends only on :mod:`av`, :mod:`numpy` and\n:mod:`torch` -- all of which run on Python 3.10--3.12. See issue #424.\n\"\"\"" + "excerpt": "\"\"\"Depth quantization/dequantization for compressed depth sidecar videos.\n\nDepth maps are packed into 12-bit integer codes (``uint16``, values\n``0…DEPTH_QMAX``) so they fit the ``gray12le`` pixel format and can be encoded\nlosslessly by HEVC (``libx265``). Logarithmic quantization is the default\nbecause it allocates more quanta to near-range depth, matching the\n``1/depth`` error profile of typical depth sensors.\n\nThe math is ported from lerobot 0.6.0 (itself adapted from BEHAVIOR-1K's\n``obs_utils.py``) and depends only on :mod:`av`, :mod:`numpy` and\n:mod:`torch` -- all of which run on Python 3.10--3.12. See issue #424.\n\"\"\"" } ], "documentation": [ @@ -5346,6 +5410,42 @@ "excerpt": " generate_urdf_collision_convexes(" } ] + }, + { + "id": "scene-attempt-imports-scene-contracts", + "source": "scene-expansion-integration", + "target": "scene-expansion", + "relation": "imports", + "description": "Consumes the shared SceneVariant proposal contract.", + "provenance": "source-reviewed", + "scope": "Explicit module import; does not move task or execution ownership into the scene proposal core.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/scene_expansion.py", + "symbol": "", + "start_line": 33, + "end_line": 33, + "excerpt": "from embodichain.lab.sim.scene_expansion import SceneVariant" + } + ] + }, + { + "id": "scene-attempt-calls-task-program-segments", + "source": "scene-expansion-integration", + "target": "embodied-env", + "relation": "calls", + "description": "Creates canonical Task Program segments for the selected program.", + "provenance": "source-reviewed", + "scope": "After injected scene preparation and initial validation pass; the existing demo executor consumes the resulting segments.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/scene_expansion.py", + "symbol": "execute_scene_variant", + "start_line": 294, + "end_line": 296, + "excerpt": " segments = EmbodiedEnv.create_demo_segments(\n target, task_program=selected_program\n )" + } + ] } ], "views": [ @@ -5372,6 +5472,7 @@ "semantic-executor", "environment-adapter", "gym-bridge", + "scene-expansion-integration", "mllm", "atomic-engine", "motion-generator", @@ -5380,6 +5481,7 @@ "workspace", "augmentation", "simulation-manager", + "scene-expansion", "robot", "sensors", "physics", @@ -5511,7 +5613,9 @@ "differentiable-trainer-constructs-observation-normalizer", "rl-algorithms-holds-ppo", "rl-algorithms-holds-grpo", - "rl-algorithms-holds-apg" + "rl-algorithms-holds-apg", + "scene-attempt-imports-scene-contracts", + "scene-attempt-calls-task-program-segments" ], "groups": [ { @@ -5542,6 +5646,7 @@ "semantic-executor", "environment-adapter", "gym-bridge", + "scene-expansion-integration", "mllm" ] }, @@ -5562,6 +5667,7 @@ "label": "Simulation world", "node_ids": [ "simulation-manager", + "scene-expansion", "robot", "sensors", "physics", @@ -5666,6 +5772,7 @@ "mllm", "program-input", "scene-registry", + "scene-expansion", "skill-profile", "integration-catalog", "task-program-config", @@ -5681,6 +5788,7 @@ "parallel-execution", "effect-evidence", "gym-bridge", + "scene-expansion-integration", "command-sink", "step-clock", "embodied-env", @@ -5715,7 +5823,9 @@ "base-constructs-simulation", "env-calls-add-robot", "mllm-imports-program-input", - "mllm-calls-environment-adapter" + "mllm-calls-environment-adapter", + "scene-attempt-imports-scene-contracts", + "scene-attempt-calls-task-program-segments" ], "groups": [ { @@ -5725,6 +5835,7 @@ "mllm", "program-input", "scene-registry", + "scene-expansion", "skill-profile", "integration-catalog" ] @@ -5770,6 +5881,7 @@ "label": "Gym integration & simulation", "node_ids": [ "gym-bridge", + "scene-expansion-integration", "command-sink", "step-clock", "embodied-env", @@ -5791,6 +5903,7 @@ "robot", "articulations", "rigid-objects", + "scene-expansion", "rigid-object-groups", "deformables", "surface-deformables", @@ -5813,6 +5926,7 @@ "differentiable-env", "autograd-bridge", "configuration", + "scene-expansion-integration", "data-assets", "event-manager", "randomization", @@ -5848,7 +5962,8 @@ "rigid-object-groups-inherits-batch-entity", "articulations-inherits-batch-entity", "deformables-inherits-batch-entity", - "lights-inherits-batch-entity" + "lights-inherits-batch-entity", + "scene-attempt-imports-scene-contracts" ], "groups": [ { @@ -5868,6 +5983,7 @@ "robot", "articulations", "rigid-objects", + "scene-expansion", "rigid-object-groups", "deformables", "surface-deformables", @@ -5914,6 +6030,7 @@ "label": "Configuration & control", "node_ids": [ "configuration", + "scene-expansion-integration", "data-assets", "event-manager", "randomization", diff --git a/docs/architecture/generated/architecture.json b/docs/architecture/generated/architecture.json index b447c0d24..1de7d4003 100644 --- a/docs/architecture/generated/architecture.json +++ b/docs/architecture/generated/architecture.json @@ -3,9 +3,9 @@ "kind": "static-architecture", "coverage": "curated-sample", "repository": "DexForce/EmbodiChain", - "revision": "abd754a3d86e82a6838e3062adda001f8b097007", - "source_ref": "HEAD", - "package_version": "0.2.4", + "revision": "61b2fbee9682ddeb7b0bc09f64c018509d18a01b", + "source_ref": "61b2fbee", + "package_version": "0.3.0", "topic_index": "agent_context/MAP.yaml", "limitations": [ "A curated source snapshot, not a complete import graph, runtime trace, or execution order.", @@ -87,8 +87,8 @@ { "path": "embodichain/toolkits/graspkit/pg_grasp/pose_generator.py", "symbol": "AntipodalGraspPoseGenerator", - "start_line": 172, - "end_line": 172, + "start_line": 177, + "end_line": 177, "excerpt": "class AntipodalGraspPoseGenerator(ParallelJawGraspPoseGenerator):" } ], @@ -145,8 +145,8 @@ { "path": "embodichain/lab/sim/objects/articulation.py", "symbol": "Articulation", - "start_line": 694, - "end_line": 694, + "start_line": 713, + "end_line": 713, "excerpt": "class Articulation(BatchEntity):" } ], @@ -203,8 +203,8 @@ { "path": "embodichain/lab/gym/envs/managers/async_datasets.py", "symbol": "AsyncLeRobotRecorder", - "start_line": 55, - "end_line": 55, + "start_line": 57, + "end_line": 57, "excerpt": "class AsyncLeRobotRecorder(LeRobotRecorder):" } ], @@ -231,8 +231,8 @@ { "path": "embodichain/lab/sim/atomic_actions/engine.py", "symbol": "AtomicActionEngine", - "start_line": 46, - "end_line": 46, + "start_line": 52, + "end_line": 52, "excerpt": "class AtomicActionEngine:" } ], @@ -266,8 +266,8 @@ { "path": "embodichain/lab/sim/motion/expansion/session.py", "symbol": "ExpansionSession", - "start_line": 108, - "end_line": 108, + "start_line": 105, + "end_line": 105, "excerpt": "class ExpansionSession:" } ], @@ -495,8 +495,8 @@ { "path": "embodichain/lab/gym/envs/task_program/bridge.py", "symbol": "BufferedGymCommandSink", - "start_line": 743, - "end_line": 743, + "start_line": 834, + "end_line": 834, "excerpt": "class BufferedGymCommandSink:" } ], @@ -624,7 +624,7 @@ "symbol": "", "start_line": 17, "end_line": 22, - "excerpt": "\"\"\"Kinematics computations and scalar motion profiles.\n\nPrivate Warp kernels implement OPW, SRS, UR, and trapezoidal/Double-S profiles.\nStateful solver interfaces and robot configuration remain in\n``embodichain.lab.sim.motion.solvers``.\nPure batched manipulability helpers live in :mod:`.manipulability`.\"\"\"" + "excerpt": "\"\"\"Kinematics computations and scalar motion profiles.\n\nPrivate Warp kernels implement FEP, OPW, SRS, UR, and trapezoidal/Double-S profiles.\nStateful solver interfaces and robot configuration remain in\n``embodichain.lab.sim.motion.solvers``.\nPure batched manipulability helpers live in :mod:`.manipulability`.\"\"\"" } ], "documentation": [ @@ -679,8 +679,8 @@ { "path": "embodichain/lab/gym/utils/gym_utils.py", "symbol": "build_env_cfg_from_args", - "start_line": 1211, - "end_line": 1211, + "start_line": 1469, + "end_line": 1469, "excerpt": "def build_env_cfg_from_args(" } ], @@ -1137,8 +1137,8 @@ { "path": "embodichain/lab/gym/envs/embodied_env.py", "symbol": "EmbodiedEnv", - "start_line": 284, - "end_line": 284, + "start_line": 299, + "end_line": 299, "excerpt": "class EmbodiedEnv(BaseEnv):" } ], @@ -1166,8 +1166,8 @@ { "path": "embodichain/lab/scripts/run_env.py", "symbol": "cli", - "start_line": 994, - "end_line": 994, + "start_line": 1641, + "end_line": 1641, "excerpt": "def cli(argv: Sequence[str] | None = None) -> None:" } ], @@ -1195,8 +1195,8 @@ { "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "TaskProgramEnvironmentAdapter", - "start_line": 295, - "end_line": 295, + "start_line": 306, + "end_line": 306, "excerpt": "class TaskProgramEnvironmentAdapter:" } ], @@ -1430,8 +1430,8 @@ { "path": "embodichain/toolkits/graspkit/pg_grasp/antipodal_sampler.py", "symbol": "AntipodalSampler", - "start_line": 52, - "end_line": 52, + "start_line": 62, + "end_line": 62, "excerpt": "class AntipodalSampler:" } ], @@ -1541,8 +1541,8 @@ { "path": "embodichain/lab/gym/envs/task_program/bridge.py", "symbol": "TaskProgramDemoBridge", - "start_line": 951, - "end_line": 951, + "start_line": 1042, + "end_line": 1042, "excerpt": "class TaskProgramDemoBridge:" } ], @@ -2108,8 +2108,8 @@ { "path": "embodichain/lab/gym/envs/managers/datasets.py", "symbol": "LeRobotRecorder", - "start_line": 101, - "end_line": 101, + "start_line": 131, + "end_line": 131, "excerpt": "class LeRobotRecorder(Functor):" } ], @@ -2416,6 +2416,72 @@ } ] }, + { + "id": "scene-expansion", + "label": "Scene expansion contracts", + "kind": "package", + "topic_ids": [ + "scene-expansion" + ], + "summary": "Defines immutable arena-local scene proposals and bounded authored candidate sources.", + "details": "The scene expansion core represents existing-entity pose changes with stable proposal identity, movable-entity permissions, provenance, and a bounded fixed-candidate source. Task semantics, physical preparation, execution, and persistence remain host responsibilities.", + "boundaries": [ + "The core does not import Task Program, GenSim or Gym and does not reset, step or validate task success." + ], + "evidence": [ + { + "path": "embodichain/lab/sim/scene_expansion/__init__.py", + "symbol": "", + "start_line": 25, + "end_line": 27, + "excerpt": "from .cfg import SceneExpansionCfg\nfrom .contracts import ScenePoseChange, SceneVariant\nfrom .source import FixedSceneCandidateSource" + }, + { + "path": "embodichain/lab/sim/scene_expansion/source.py", + "symbol": "FixedSceneCandidateSource", + "start_line": 30, + "end_line": 30, + "excerpt": "class FixedSceneCandidateSource:" + } + ], + "documentation": [ + { + "docname": "api_reference/embodichain/embodichain.lab.sim.scene_expansion", + "label": "Scene expansion contracts and fixed candidates", + "source_extension": ".rst" + } + ] + }, + { + "id": "scene-expansion-integration", + "label": "Scene expansion attempt", + "kind": "function", + "topic_ids": [ + "scene-expansion", + "task-programs" + ], + "summary": "Prepares one scene candidate and executes a Task Program with separate measured acceptance.", + "details": "execute_scene_variant connects a scene proposal to injected preparation and measurement callbacks. For a valid initial state it creates the canonical Task Program segments and uses the existing Gym demo executor, recording separate initial-state, program, and measured-outcome evidence.", + "boundaries": [ + "One B=1 attempt; the caller supplies physical preparation, measured task evidence, retry policy, and the reset-time persistence transaction." + ], + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/scene_expansion.py", + "symbol": "execute_scene_variant", + "start_line": 195, + "end_line": 195, + "excerpt": "def execute_scene_variant(" + } + ], + "documentation": [ + { + "docname": "api_reference/embodichain/embodichain.lab.task_program.integrations.scene_expansion", + "label": "Task Program scene preparation and measured acceptance", + "source_extension": ".rst" + } + ] + }, { "id": "scene-export", "label": "Scene export", @@ -2668,8 +2734,8 @@ { "path": "embodichain/lab/task_program/runtime/executor.py", "symbol": "SemanticCallExecutor", - "start_line": 216, - "end_line": 216, + "start_line": 225, + "end_line": 225, "excerpt": "class SemanticCallExecutor:" } ], @@ -2760,8 +2826,8 @@ { "path": "embodichain/lab/task_program/integrations/simulation/environment.py", "symbol": "SimulationTaskProgramFactory", - "start_line": 407, - "end_line": 407, + "start_line": 498, + "end_line": 498, "excerpt": "class SimulationTaskProgramFactory(TaskProgramEnvironmentFactory):" } ], @@ -2854,7 +2920,7 @@ "symbol": "", "start_line": 17, "end_line": 20, - "excerpt": "\"\"\"Inverse-kinematics solvers sharing the ``BaseSolver`` interface.\n\nProvides analytic (SRS, OPW, UR), numerical (Pinocchio, Pink, Differential), and learning-based (PyTorch, NeuralIK) solvers, one per robot control part.\n\"\"\"" + "excerpt": "\"\"\"Inverse-kinematics solvers sharing the ``BaseSolver`` interface.\n\nProvides analytic (SRS, OPW, UR, FEP), numerical (Pinocchio, Pink, Differential), and learning-based (PyTorch, NeuralIK) solvers, one per robot control part.\n\"\"\"" } ], "documentation": [ @@ -2881,8 +2947,8 @@ { "path": "embodichain/lab/gym/envs/task_program/bridge.py", "symbol": "EnvironmentStepClock", - "start_line": 325, - "end_line": 325, + "start_line": 331, + "end_line": 331, "excerpt": "class EnvironmentStepClock(ExecutionClock):" } ], @@ -3376,8 +3442,8 @@ { "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "TaskProgramEnvironmentAdapter.compile", - "start_line": 559, - "end_line": 559, + "start_line": 570, + "end_line": 570, "excerpt": " return TaskProgramCompiler.from_scene_registry(registry).compile(program)" } ] @@ -3394,8 +3460,8 @@ { "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "TaskProgramEnvironmentAdapter.create_bridge", - "start_line": 811, - "end_line": 816, + "start_line": 880, + "end_line": 885, "excerpt": " return TaskProgramDemoBridge(\n program,\n assembly.runtime,\n assembly.command_sink,\n assembly.clock,\n post_policy_port=self._post_policy_port," } ] @@ -3412,8 +3478,8 @@ { "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "TaskProgramEnvironmentAdapter._assemble_execution_runtime", - "start_line": 666, - "end_line": 666, + "start_line": 696, + "end_line": 696, "excerpt": " clock = EnvironmentStepClock(self._step_dt)" } ] @@ -3430,9 +3496,9 @@ { "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "TaskProgramEnvironmentAdapter._assemble_execution_runtime", - "start_line": 740, - "end_line": 747, - "excerpt": " runtime = SemanticCallExecutor(\n semantic.compiler,\n observation_provider,\n command_sink,\n evidence_collector,\n clock=clock,\n runner_cfg=deepcopy(selected_runner_cfg),\n )" + "start_line": 770, + "end_line": 778, + "excerpt": " runtime = SemanticCallExecutor(\n semantic.compiler,\n observation_provider,\n command_sink,\n evidence_collector,\n clock=clock,\n runner_cfg=deepcopy(selected_runner_cfg),\n plan_transform_factory=plan_transform_factory,\n )" } ] }, @@ -3448,8 +3514,8 @@ { "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "TaskProgramEnvironmentAdapter._assemble_execution_runtime", - "start_line": 736, - "end_line": 736, + "start_line": 766, + "end_line": 766, "excerpt": " command_sink = BufferedGymCommandSink(command_encoder, clock)" } ] @@ -3466,8 +3532,8 @@ { "path": "embodichain/toolkits/graspkit/pg_grasp/pose_generator.py", "symbol": "AntipodalGraspPoseGenerator._backend", - "start_line": 303, - "end_line": 303, + "start_line": 308, + "end_line": 308, "excerpt": " backend = _AntipodalMeshBackend(" } ] @@ -3502,8 +3568,8 @@ { "path": "embodichain/toolkits/graspkit/pg_grasp/pose_generator.py", "symbol": "AntipodalGraspPoseGenerator._backend", - "start_line": 299, - "end_line": 299, + "start_line": 304, + "end_line": 304, "excerpt": " model = self._gripper_model" } ] @@ -3520,8 +3586,8 @@ { "path": "embodichain/lab/sim/objects/articulation.py", "symbol": "Articulation", - "start_line": 694, - "end_line": 694, + "start_line": 713, + "end_line": 713, "excerpt": "class Articulation(BatchEntity):" } ] @@ -3538,8 +3604,8 @@ { "path": "embodichain/lab/gym/envs/managers/async_datasets.py", "symbol": "AsyncLeRobotRecorder", - "start_line": 55, - "end_line": 55, + "start_line": 57, + "end_line": 57, "excerpt": "class AsyncLeRobotRecorder(LeRobotRecorder):" } ] @@ -3574,8 +3640,8 @@ { "path": "embodichain/lab/gym/envs/task_program/bridge.py", "symbol": "TaskProgramDemoBridge.__init__", - "start_line": 1015, - "end_line": 1015, + "start_line": 1119, + "end_line": 1119, "excerpt": " self._clock = clock" } ] @@ -3592,15 +3658,15 @@ { "path": "embodichain/lab/gym/envs/task_program/bridge.py", "symbol": "TaskProgramDemoBridge.__init__", - "start_line": 1013, - "end_line": 1013, + "start_line": 1117, + "end_line": 1117, "excerpt": " self._runtime = runtime" }, { "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "TaskProgramEnvironmentAdapter._assemble_execution_runtime", - "start_line": 740, - "end_line": 741, + "start_line": 770, + "end_line": 771, "excerpt": " runtime = SemanticCallExecutor(\n semantic.compiler," } ] @@ -3617,15 +3683,15 @@ { "path": "embodichain/lab/gym/envs/task_program/bridge.py", "symbol": "TaskProgramDemoBridge.__init__", - "start_line": 1012, - "end_line": 1012, + "start_line": 1116, + "end_line": 1116, "excerpt": " self._program = program" }, { "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "TaskProgramEnvironmentAdapter.create_bridge", - "start_line": 811, - "end_line": 813, + "start_line": 880, + "end_line": 882, "excerpt": " return TaskProgramDemoBridge(\n program,\n assembly.runtime," } ] @@ -3642,8 +3708,8 @@ { "path": "embodichain/lab/gym/envs/task_program/bridge.py", "symbol": "TaskProgramDemoBridge.__init__", - "start_line": 1014, - "end_line": 1014, + "start_line": 1118, + "end_line": 1118, "excerpt": " self._sink = command_sink" } ] @@ -3660,8 +3726,8 @@ { "path": "embodichain/lab/gym/envs/task_program/bridge.py", "symbol": "TaskProgramDemoBridge._segment_actions", - "start_line": 1281, - "end_line": 1281, + "start_line": 1393, + "end_line": 1393, "excerpt": " self._sink.pop()," } ] @@ -3929,8 +3995,8 @@ { "path": "embodichain/lab/gym/utils/gym_utils.py", "symbol": "config_to_cfg", - "start_line": 514, - "end_line": 515, + "start_line": 527, + "end_line": 528, "excerpt": " component_resolution = _resolve_gym_components(config, base_dir=base_dir)\n config = component_resolution.config" } ] @@ -3947,22 +4013,22 @@ { "path": "embodichain/lab/gym/utils/gym_utils.py", "symbol": "config_to_cfg", - "start_line": 489, - "end_line": 489, + "start_line": 502, + "end_line": 502, "excerpt": " env_cfg = EmbodiedEnvCfg()" }, { "path": "embodichain/lab/gym/utils/gym_utils.py", "symbol": "build_env_cfg_from_args", - "start_line": 1245, - "end_line": 1250, - "excerpt": " cfg: EmbodiedEnvCfg = config_to_cfg(\n gym_config,\n manager_modules=get_manager_modules(),\n source_path=gym_config_source_path,\n task_program_path_override=getattr(args, \"task_program\", None),\n )" + "start_line": 1509, + "end_line": 1514, + "excerpt": " cfg: EmbodiedEnvCfg = config_to_cfg(\n gym_config,\n manager_modules=get_manager_modules(),\n source_path=gym_config_source_path,\n task_program_path_override=getattr(args, \"task_program\", None),\n )" }, { "path": "embodichain/lab/scripts/run_env.py", "symbol": "cli", - "start_line": 1028, - "end_line": 1028, + "start_line": 1722, + "end_line": 1722, "excerpt": " env = gymnasium.make(id=gym_config[\"id\"], cfg=env_cfg, **action_config)" } ] @@ -4159,8 +4225,8 @@ { "path": "pyproject.toml", "symbol": "", - "start_line": 79, - "end_line": 80, + "start_line": 82, + "end_line": 83, "excerpt": "[project.entry-points.\"embodichain.tasks\"]\nembodichain_tasks = \"embodichain_tasks\"" }, { @@ -4198,8 +4264,8 @@ { "path": "embodichain/lab/gym/envs/embodied_env.py", "symbol": "EmbodiedEnv.__init__", - "start_line": 407, - "end_line": 407, + "start_line": 427, + "end_line": 427, "excerpt": " self.dataset_manager = DatasetManager(self.cfg.dataset, self)" } ] @@ -4216,8 +4282,8 @@ { "path": "embodichain/lab/gym/envs/embodied_env.py", "symbol": "EmbodiedEnv._init_sim_state", - "start_line": 707, - "end_line": 707, + "start_line": 735, + "end_line": 735, "excerpt": " self.event_manager = EventManager(self.cfg.events, self)" } ] @@ -4234,8 +4300,8 @@ { "path": "embodichain/lab/sim/atomic_actions/engine.py", "symbol": "AtomicActionEngine.motion_generator", - "start_line": 93, - "end_line": 93, + "start_line": 99, + "end_line": 99, "excerpt": " return self._planning_services.motion_generator" } ] @@ -4252,9 +4318,9 @@ { "path": "embodichain/lab/gym/envs/embodied_env.py", "symbol": "EmbodiedEnv.create_task_program_bridge", - "start_line": 2247, - "end_line": 2247, - "excerpt": " return self._checked_task_program_adapter().create_bridge(program)" + "start_line": 2670, + "end_line": 2670, + "excerpt": " return self._checked_task_program_adapter().create_bridge(" } ] }, @@ -4270,8 +4336,8 @@ { "path": "embodichain/lab/gym/envs/embodied_env.py", "symbol": "EmbodiedEnv.compile_task_program", - "start_line": 2232, - "end_line": 2232, + "start_line": 2648, + "end_line": 2648, "excerpt": " return self._checked_task_program_adapter().compile(program)" } ] @@ -4288,8 +4354,8 @@ { "path": "embodichain/lab/gym/envs/embodied_env.py", "symbol": "EmbodiedEnv._declare_robot", - "start_line": 1991, - "end_line": 1991, + "start_line": 2402, + "end_line": 2402, "excerpt": " return self.sim.add_robot(self.cfg.robot)" } ] @@ -4306,8 +4372,8 @@ { "path": "embodichain/lab/gym/envs/embodied_env.py", "symbol": "EmbodiedEnv", - "start_line": 284, - "end_line": 284, + "start_line": 299, + "end_line": 299, "excerpt": "class EmbodiedEnv(BaseEnv):" } ] @@ -4324,8 +4390,8 @@ { "path": "embodichain/lab/gym/envs/managers/event_manager.py", "symbol": "EventManager._call_event_functor", - "start_line": 489, - "end_line": 489, + "start_line": 501, + "end_line": 501, "excerpt": " return super()._call_functor(functor_name, functor_cfg, *args)" }, { @@ -4349,9 +4415,9 @@ { "path": "embodichain/lab/task_program/runtime/executor.py", "symbol": "SemanticCallExecutor._prepare_grounded_call", - "start_line": 940, - "end_line": 944, - "excerpt": " session = self._engine.start(\n (invocation,),\n context,\n eligible_mask=active_mask,\n )" + "start_line": 999, + "end_line": 1004, + "excerpt": " session = self._engine.start(\n (invocation,),\n context,\n eligible_mask=active_mask,\n plan_transform=plan_transform,\n )" } ] }, @@ -4367,8 +4433,8 @@ { "path": "embodichain/lab/task_program/runtime/executor.py", "symbol": "SemanticCallExecutor._prepare_grounded_call", - "start_line": 946, - "end_line": 952, + "start_line": 1006, + "end_line": 1012, "excerpt": " runner = ExecutionRunner(\n session,\n primed,\n self._command_sink,\n clock=self._clock,\n cfg=runner_cfg,\n )" } ] @@ -4385,8 +4451,8 @@ { "path": "embodichain/lab/task_program/runtime/executor.py", "symbol": "SemanticCallExecutor.__init__", - "start_line": 264, - "end_line": 264, + "start_line": 282, + "end_line": 282, "excerpt": " self._compiler = compiler" } ] @@ -4403,15 +4469,15 @@ { "path": "embodichain/lab/task_program/runtime/executor.py", "symbol": "SemanticCallExecutor.__init__", - "start_line": 251, - "end_line": 251, + "start_line": 269, + "end_line": 269, "excerpt": " engine = integration.engine" }, { "path": "embodichain/lab/task_program/runtime/executor.py", "symbol": "SemanticCallExecutor.__init__", - "start_line": 265, - "end_line": 265, + "start_line": 283, + "end_line": 283, "excerpt": " self._engine = engine" } ] @@ -4428,8 +4494,8 @@ { "path": "embodichain/lab/task_program/integrations/simulation/environment.py", "symbol": "SimulationTaskProgramFactory.create_atomic_action_engine", - "start_line": 647, - "end_line": 651, + "start_line": 749, + "end_line": 753, "excerpt": " engine = AtomicActionEngine(\n motion_generator,\n control_profiles=profile.action_control_profiles(),\n grasp_pose_generators=self._grasp_pose_generators,\n )" } ] @@ -4446,8 +4512,8 @@ { "path": "embodichain/lab/task_program/integrations/simulation/environment.py", "symbol": "SimulationTaskProgramFactory._create_motion_generator", - "start_line": 788, - "end_line": 788, + "start_line": 892, + "end_line": 892, "excerpt": " generator = MotionGenerator(MotionGenCfg(planner_cfg=planner_cfg))" } ] @@ -4518,8 +4584,8 @@ { "path": "embodichain/toolkits/graspkit/pg_grasp/_antipodal_backend.py", "symbol": "_AntipodalMeshBackend.__init__", - "start_line": 118, - "end_line": 118, + "start_line": 119, + "end_line": 119, "excerpt": " self._antipodal_sampler = AntipodalSampler(cfg=sampler_cfg)" } ] @@ -4536,8 +4602,8 @@ { "path": "embodichain/lab/scripts/run_env.py", "symbol": "cli", - "start_line": 1023, - "end_line": 1023, + "start_line": 1670, + "end_line": 1670, "excerpt": " env_cfg, gym_config, action_config = build_env_cfg_from_args(args)" } ] @@ -4554,8 +4620,8 @@ { "path": "embodichain/lab/scripts/run_env.py", "symbol": "cli", - "start_line": 1018, - "end_line": 1018, + "start_line": 1665, + "end_line": 1665, "excerpt": " discover_task_packages()" } ] @@ -4590,8 +4656,8 @@ { "path": "embodichain/lab/gym/envs/managers/__init__.py", "symbol": "", - "start_line": 38, - "end_line": 38, + "start_line": 39, + "end_line": 39, "excerpt": "from .datasets import LeRobotRecorder" } ] @@ -4662,8 +4728,8 @@ { "path": "embodichain/lab/gym/envs/managers/datasets.py", "symbol": "LeRobotRecorder._initialize_dataset", - "start_line": 1142, - "end_line": 1142, + "start_line": 1544, + "end_line": 1544, "excerpt": " self._depth_manager = DepthSidecarManager(" } ] @@ -4824,8 +4890,8 @@ { "path": "embodichain/learning/rl/utils/trainer.py", "symbol": "Trainer.__init__", - "start_line": 136, - "end_line": 136, + "start_line": 137, + "end_line": 137, "excerpt": " self.collector = SyncCollector(" } ] @@ -4842,8 +4908,8 @@ { "path": "embodichain/learning/rl/utils/trainer.py", "symbol": "Trainer.__init__", - "start_line": 123, - "end_line": 123, + "start_line": 124, + "end_line": 124, "excerpt": " self.buffer = RolloutBuffer(" } ] @@ -4975,8 +5041,8 @@ { "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "TaskProgramEnvironmentAdapter._assemble_execution_runtime", - "start_line": 736, - "end_line": 736, + "start_line": 766, + "end_line": 766, "excerpt": " command_sink = BufferedGymCommandSink(command_encoder, clock)" } ] @@ -4999,6 +5065,42 @@ } ] }, + { + "id": "scene-attempt-calls-task-program-segments", + "source": "scene-expansion-integration", + "target": "embodied-env", + "relation": "calls", + "description": "Creates canonical Task Program segments for the selected program.", + "provenance": "source-reviewed", + "scope": "After injected scene preparation and initial validation pass; the existing demo executor consumes the resulting segments.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/scene_expansion.py", + "symbol": "execute_scene_variant", + "start_line": 294, + "end_line": 296, + "excerpt": " segments = EmbodiedEnv.create_demo_segments(\n target, task_program=selected_program\n )" + } + ] + }, + { + "id": "scene-attempt-imports-scene-contracts", + "source": "scene-expansion-integration", + "target": "scene-expansion", + "relation": "imports", + "description": "Consumes the shared SceneVariant proposal contract.", + "provenance": "source-reviewed", + "scope": "Explicit module import; does not move task or execution ownership into the scene proposal core.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/scene_expansion.py", + "symbol": "", + "start_line": 33, + "end_line": 33, + "excerpt": "from embodichain.lab.sim.scene_expansion import SceneVariant" + } + ] + }, { "id": "scene-editing-calls-scene-layout", "source": "scene-editing", @@ -5011,8 +5113,8 @@ { "path": "embodichain/gen_sim/scene_engine/pipeline/edit.py", "symbol": "edit_scene", - "start_line": 106, - "end_line": 106, + "start_line": 110, + "end_line": 110, "excerpt": " post_edit_scene = edit_layout(" } ] @@ -5029,8 +5131,8 @@ { "path": "embodichain/gen_sim/scene_engine/pipeline/edit.py", "symbol": "edit_scene", - "start_line": 62, - "end_line": 62, + "start_line": 66, + "end_line": 66, "excerpt": " scene_importer = SceneExportImporter(output_root=output_root)" } ] @@ -5065,8 +5167,8 @@ { "path": "embodichain/gen_sim/scene_engine/pipeline/generate.py", "symbol": "generate_scene_from_image", - "start_line": 86, - "end_line": 86, + "start_line": 90, + "end_line": 90, "excerpt": " scene = generate_scene_and_refine(" } ] @@ -5083,8 +5185,8 @@ { "path": "embodichain/gen_sim/scene_engine/pipeline/generate.py", "symbol": "generate_scene_from_image", - "start_line": 64, - "end_line": 64, + "start_line": 68, + "end_line": 68, "excerpt": " scene, scene_graph = understand_scene(" } ] @@ -5137,15 +5239,15 @@ { "path": "embodichain/lab/sim/sim_manager.py", "symbol": "SimulationManager.add_robot", - "start_line": 3043, - "end_line": 3044, + "start_line": 3142, + "end_line": 3143, "excerpt": " robot = self._declare_spawn_articulation(cfg, Robot)\n self._robots[uid] = robot" }, { "path": "embodichain/lab/sim/sim_manager.py", "symbol": "SimulationManager._declare_spawn_articulation", - "start_line": 3077, - "end_line": 3080, + "start_line": 3176, + "end_line": 3179, "excerpt": " facade = facade_type(\n cfg=cfg,\n device=self.device,\n )" } ] @@ -5162,8 +5264,8 @@ { "path": "embodichain/lab/sim/sim_manager.py", "symbol": "SimulationManager.start_visualization", - "start_line": 1008, - "end_line": 1012, + "start_line": 1023, + "end_line": 1027, "excerpt": " runtime = VisualizationRuntime(\n SceneExporter(self, visualization_cfg),\n visualization_cfg,\n )\n runtime.start()" } ] @@ -5180,8 +5282,8 @@ { "path": "embodichain/lab/sim/sim_manager.py", "symbol": "SimulationManager.add_robot", - "start_line": 3044, - "end_line": 3047, + "start_line": 3143, + "end_line": 3146, "excerpt": " self._robots[uid] = robot\n if was_materialized:\n self.prepare()\n return robot" } ] @@ -5198,8 +5300,8 @@ { "path": "embodichain/lab/sim/sim_manager.py", "symbol": "SimulationManager.enable_gizmo", - "start_line": 3230, - "end_line": 3230, + "start_line": 3329, + "end_line": 3329, "excerpt": " gizmo = Gizmo(target, gizmo_cfg, control_part)" } ] @@ -5216,8 +5318,8 @@ { "path": "embodichain/lab/sim/sim_manager.py", "symbol": "SimulationManager.create_rigid_constraint", - "start_line": 2746, - "end_line": 2746, + "start_line": 2845, + "end_line": 2845, "excerpt": " constraint = RigidConstraint(" } ] @@ -5234,8 +5336,8 @@ { "path": "embodichain/lab/sim/sim_manager.py", "symbol": "SimulationManager.add_rigid_object_group", - "start_line": 2869, - "end_line": 2869, + "start_line": 2968, + "end_line": 2968, "excerpt": " group = RigidObjectGroup(" } ] @@ -5252,8 +5354,8 @@ { "path": "embodichain/lab/sim/sim_manager.py", "symbol": "SimulationManager._declare_rigid_objects", - "start_line": 2440, - "end_line": 2440, + "start_line": 2539, + "end_line": 2539, "excerpt": " rigid_obj = RigidObject(cfg=cfg, device=self.device)" } ] @@ -5270,8 +5372,8 @@ { "path": "embodichain/lab/sim/sim_manager.py", "symbol": "SimulationManager.add_articulation", - "start_line": 2968, - "end_line": 2968, + "start_line": 3067, + "end_line": 3067, "excerpt": " self._articulations[uid] = articulation" } ] @@ -5288,8 +5390,8 @@ { "path": "embodichain/lab/sim/sim_manager.py", "symbol": "SimulationManager.add_deformable_object", - "start_line": 2546, - "end_line": 2546, + "start_line": 2645, + "end_line": 2645, "excerpt": " self._deformable_objects[uid] = deformable" } ] @@ -5306,8 +5408,8 @@ { "path": "embodichain/lab/sim/sim_manager.py", "symbol": "SimulationManager.add_light", - "start_line": 2186, - "end_line": 2186, + "start_line": 2285, + "end_line": 2285, "excerpt": " self._lights[uid] = batch_lights" } ] @@ -5324,8 +5426,8 @@ { "path": "embodichain/lab/gym/envs/task_program/bridge.py", "symbol": "BufferedGymCommandSink.__init__", - "start_line": 759, - "end_line": 760, + "start_line": 850, + "end_line": 851, "excerpt": " self._encoder = encoder\n self._clock = clock" } ] @@ -5456,6 +5558,24 @@ } ] }, + { + "id": "static-imports-0f0d7a478bf8", + "source": "simulation-factory", + "target": "semantic-compiler", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/simulation/environment.py", + "symbol": "", + "start_line": 64, + "end_line": 66, + "excerpt": "from embodichain.lab.task_program.compiler.lowering import (\n RegisteredSemanticLowerer,\n)" + } + ] + }, { "id": "static-imports-1458e07ef4ce", "source": "grasp-backend", @@ -5475,20 +5595,20 @@ ] }, { - "id": "static-imports-16d95b2b8cb0", - "source": "simulation-factory", - "target": "effect-evidence", + "id": "static-imports-14722eceaa11", + "source": "environment-adapter", + "target": "semantic-compiler", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/task_program/integrations/simulation/environment.py", + "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "", - "start_line": 59, - "end_line": 64, - "excerpt": "from embodichain.lab.task_program.semantics.evidence import (\n ControlPartRobotEvidenceSource,\n ControlPartSimulationEvidenceProvider,\n EffectEvidenceProvider,\n SceneArticulationEvidenceProvider,\n)" + "start_line": 51, + "end_line": 56, + "excerpt": "from embodichain.lab.task_program.compiler.lowering import (\n HandOverPoseProvider,\n RegisteredSemanticLowerer,\n RelationTargetGrounder,\n SemanticCallCompiler,\n)" } ] }, @@ -5511,27 +5631,9 @@ ] }, { - "id": "static-imports-1a349a9bf733", - "source": "simulation-factory", - "target": "scene-registry", - "relation": "imports", - "description": "The selected source module declares an absolute import associated with this target.", - "provenance": "static-extracted", - "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", - "evidence": [ - { - "path": "embodichain/lab/task_program/integrations/simulation/environment.py", - "symbol": "", - "start_line": 69, - "end_line": 72, - "excerpt": "from embodichain.lab.task_program.semantics.scene import (\n RegistrySceneProvider,\n SceneRegistry,\n)" - } - ] - }, - { - "id": "static-imports-1c2288600190", + "id": "static-imports-19c0e59c86ea", "source": "articulations", - "target": "object-backends", + "target": "batch-entity", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", @@ -5542,43 +5644,43 @@ "symbol": "", "start_line": 56, "end_line": 56, - "excerpt": "from embodichain.lab.sim.objects.backends import SceneArticulationView" + "excerpt": "from embodichain.lab.sim.common import BatchEntity" } ] }, { - "id": "static-imports-1d1929c1f98e", - "source": "environment-adapter", - "target": "semantic-executor", + "id": "static-imports-1bb109404700", + "source": "simulation-factory", + "target": "simulation-manager", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", - "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", + "scope": "Module-level declaration under if TYPE_CHECKING. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/task_program/integrations/environment.py", + "path": "embodichain/lab/task_program/integrations/simulation/environment.py", "symbol": "", - "start_line": 72, - "end_line": 72, - "excerpt": "from embodichain.lab.task_program.runtime.executor import SemanticCallExecutor" + "start_line": 98, + "end_line": 98, + "excerpt": " from embodichain.lab.sim.sim_manager import SimulationManager" } ] }, { - "id": "static-imports-22b538406c16", - "source": "articulations", - "target": "batch-entity", + "id": "static-imports-218cb7b6a16d", + "source": "scene-editing", + "target": "scene-layout", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/sim/objects/articulation.py", + "path": "embodichain/gen_sim/scene_engine/pipeline/edit.py", "symbol": "", - "start_line": 55, - "end_line": 55, - "excerpt": "from embodichain.lab.sim.common import BatchEntity" + "start_line": 49, + "end_line": 51, + "excerpt": "from embodichain.gen_sim.scene_engine.pipeline.editing.scene_edit_layout_generation import (\n edit_layout,\n)" } ] }, @@ -5618,6 +5720,24 @@ } ] }, + { + "id": "static-imports-2549d7070bdd", + "source": "simulation-factory", + "target": "scene-registry", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/simulation/environment.py", + "symbol": "", + "start_line": 77, + "end_line": 82, + "excerpt": "from embodichain.lab.task_program.semantics.scene import (\n SceneCollisionRole,\n SceneCollisionWorldMode,\n RegistrySceneProvider,\n SceneRegistry,\n)" + } + ] + }, { "id": "static-imports-25644243d165", "source": "simulation-factory", @@ -5672,24 +5792,6 @@ } ] }, - { - "id": "static-imports-292707ad785d", - "source": "simulation-factory", - "target": "simulation-manager", - "relation": "imports", - "description": "The selected source module declares an absolute import associated with this target.", - "provenance": "static-extracted", - "scope": "Module-level declaration under if TYPE_CHECKING. Not a runtime call or proof that every class in the file uses the imported symbol.", - "evidence": [ - { - "path": "embodichain/lab/task_program/integrations/simulation/environment.py", - "symbol": "", - "start_line": 88, - "end_line": 88, - "excerpt": " from embodichain.lab.sim.sim_manager import SimulationManager" - } - ] - }, { "id": "static-imports-2b77ea1ae6d0", "source": "grasp-tools", @@ -5745,27 +5847,27 @@ ] }, { - "id": "static-imports-35d217cf64ea", - "source": "differentiable-trainer", - "target": "rl-algorithms", + "id": "static-imports-304018fdd2a7", + "source": "environment-adapter", + "target": "command-sink", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/learning/rl/differentiable_trainer.py", + "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "", - "start_line": 30, - "end_line": 30, - "excerpt": "from embodichain.learning.rl.algo import APG" + "start_line": 81, + "end_line": 92, + "excerpt": "from embodichain.lab.gym.envs.task_program.bridge import (\n TaskProgramDemoBridge,\n BufferedGymCommandSink,\n CurrentQposProvider,\n TaskProgramBridgeError,\n EnvironmentStepClock,\n JointPositionGymTransportEncoder,\n RuntimeCommandFrameEncoder,\n RuntimeTransportActionEncoder,\n SegmentPostPolicyPort,\n SegmentValidatorPort,\n)" } ] }, { - "id": "static-imports-36b2619c635f", + "id": "static-imports-32e084e6bfb3", "source": "embodied-env", - "target": "config-loader", + "target": "sensors", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", @@ -5774,45 +5876,63 @@ { "path": "embodichain/lab/gym/envs/embodied_env.py", "symbol": "", - "start_line": 83, - "end_line": 86, - "excerpt": "from embodichain.lab.gym.utils.gym_utils import (\n build_trajectory_buffer,\n init_rollout_buffer_from_gym_space,\n)" + "start_line": 53, + "end_line": 53, + "excerpt": "from embodichain.lab.sim.sensors import BaseSensor, SensorCfg" } ] }, { - "id": "static-imports-36d8a240dfd7", - "source": "atomic-engine", - "target": "motion-generator", + "id": "static-imports-34a290f7867e", + "source": "environment-adapter", + "target": "parallel-execution", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", - "scope": "Module-level declaration under if TYPE_CHECKING. Not a runtime call or proof that every class in the file uses the imported symbol.", + "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/sim/atomic_actions/engine.py", + "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "", - "start_line": 40, - "end_line": 40, - "excerpt": " from embodichain.lab.sim.motion.motion_generator import MotionGenerator" + "start_line": 66, + "end_line": 69, + "excerpt": "from embodichain.lab.task_program.runtime.parallel_executor import (\n ParallelCommandSafetyValidator,\n analyze_parallel_branches,\n)" } ] }, { - "id": "static-imports-374116b69d45", - "source": "environment-adapter", - "target": "skill-profile", + "id": "static-imports-35d217cf64ea", + "source": "differentiable-trainer", + "target": "rl-algorithms", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/task_program/integrations/environment.py", + "path": "embodichain/learning/rl/differentiable_trainer.py", "symbol": "", - "start_line": 67, - "end_line": 71, - "excerpt": "from embodichain.lab.task_program.semantics.profiles import (\n ResourceEndpoint,\n ResourceEndpointAdapter,\n RobotSkillProfile,\n)" + "start_line": 30, + "end_line": 30, + "excerpt": "from embodichain.learning.rl.algo import APG" + } + ] + }, + { + "id": "static-imports-36d8a240dfd7", + "source": "atomic-engine", + "target": "motion-generator", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration under if TYPE_CHECKING. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/sim/atomic_actions/engine.py", + "symbol": "", + "start_line": 40, + "end_line": 40, + "excerpt": " from embodichain.lab.sim.motion.motion_generator import MotionGenerator" } ] }, @@ -5924,6 +6044,24 @@ } ] }, + { + "id": "static-imports-416b1c69c882", + "source": "simulation-factory", + "target": "parallel-execution", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/simulation/environment.py", + "symbol": "", + "start_line": 73, + "end_line": 75, + "excerpt": "from embodichain.lab.task_program.runtime.parallel_executor import (\n ParallelCommandSafetyValidator,\n)" + } + ] + }, { "id": "static-imports-41cc9bb00185", "source": "semantic-executor", @@ -5978,6 +6116,24 @@ } ] }, + { + "id": "static-imports-5062dce2cb84", + "source": "embodied-env", + "target": "gym-bridge", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration under if TYPE_CHECKING. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/gym/envs/embodied_env.py", + "symbol": "", + "start_line": 103, + "end_line": 103, + "excerpt": " from embodichain.lab.gym.envs.task_program.bridge import TaskProgramDemoBridge" + } + ] + }, { "id": "static-imports-518fab548e08", "source": "scene-registry", @@ -6068,24 +6224,6 @@ } ] }, - { - "id": "static-imports-5ce7e9c376fd", - "source": "environment-adapter", - "target": "step-clock", - "relation": "imports", - "description": "The selected source module declares an absolute import associated with this target.", - "provenance": "static-extracted", - "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", - "evidence": [ - { - "path": "embodichain/lab/task_program/integrations/environment.py", - "symbol": "", - "start_line": 75, - "end_line": 86, - "excerpt": "from embodichain.lab.gym.envs.task_program.bridge import (\n TaskProgramDemoBridge,\n BufferedGymCommandSink,\n CurrentQposProvider,\n TaskProgramBridgeError,\n EnvironmentStepClock,\n JointPositionGymTransportEncoder,\n RuntimeCommandFrameEncoder,\n RuntimeTransportActionEncoder,\n SegmentPostPolicyPort,\n SegmentValidatorPort,\n)" - } - ] - }, { "id": "static-imports-5d4a690b4f47", "source": "integration-catalog", @@ -6105,38 +6243,38 @@ ] }, { - "id": "static-imports-5eb4989e8f9a", - "source": "simulation-factory", - "target": "skill-profile", + "id": "static-imports-5f67f65ae25d", + "source": "urdf-assembly", + "target": "urdf-connections", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/task_program/integrations/simulation/environment.py", + "path": "embodichain/toolkits/urdf_assembly/urdf_assembly_manager.py", "symbol": "", - "start_line": 68, - "end_line": 68, - "excerpt": "from embodichain.lab.task_program.semantics.profiles import RobotSkillProfile" + "start_line": 45, + "end_line": 47, + "excerpt": "from embodichain.toolkits.urdf_assembly.connection import (\n URDFConnectionManager,\n)" } ] }, { - "id": "static-imports-5f67f65ae25d", - "source": "urdf-assembly", - "target": "urdf-connections", + "id": "static-imports-62adb31d7b9c", + "source": "environment-adapter", + "target": "execution-runner", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/toolkits/urdf_assembly/urdf_assembly_manager.py", + "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "", - "start_line": 45, - "end_line": 47, - "excerpt": "from embodichain.toolkits.urdf_assembly.connection import (\n URDFConnectionManager,\n)" + "start_line": 38, + "end_line": 41, + "excerpt": "from embodichain.lab.sim.atomic_actions.runner import (\n ExecutionRunnerCfg,\n ObservationProvider,\n)" } ] }, @@ -6159,9 +6297,9 @@ ] }, { - "id": "static-imports-662f359eaf35", + "id": "static-imports-635bb46d2393", "source": "environment-adapter", - "target": "parallel-execution", + "target": "step-clock", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", @@ -6170,9 +6308,9 @@ { "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "", - "start_line": 63, - "end_line": 66, - "excerpt": "from embodichain.lab.task_program.runtime.parallel_executor import (\n ParallelCommandSafetyValidator,\n analyze_parallel_branches,\n)" + "start_line": 81, + "end_line": 92, + "excerpt": "from embodichain.lab.gym.envs.task_program.bridge import (\n TaskProgramDemoBridge,\n BufferedGymCommandSink,\n CurrentQposProvider,\n TaskProgramBridgeError,\n EnvironmentStepClock,\n JointPositionGymTransportEncoder,\n RuntimeCommandFrameEncoder,\n RuntimeTransportActionEncoder,\n SegmentPostPolicyPort,\n SegmentValidatorPort,\n)" } ] }, @@ -6248,24 +6386,6 @@ } ] }, - { - "id": "static-imports-6b4f407022e8", - "source": "env-launcher", - "target": "config-loader", - "relation": "imports", - "description": "The selected source module declares an absolute import associated with this target.", - "provenance": "static-extracted", - "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", - "evidence": [ - { - "path": "embodichain/lab/scripts/run_env.py", - "symbol": "", - "start_line": 40, - "end_line": 44, - "excerpt": "from embodichain.lab.gym.utils.gym_utils import (\n add_env_launcher_args_to_parser,\n build_env_cfg_from_args,\n load_trajectory,\n)" - } - ] - }, { "id": "static-imports-6e4557b26941", "source": "differentiable-collector", @@ -6285,38 +6405,38 @@ ] }, { - "id": "static-imports-6efc6c8fee5c", - "source": "embodied-env", - "target": "sensors", + "id": "static-imports-70d934a6079b", + "source": "environment-adapter", + "target": "task-program-config", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/gym/envs/embodied_env.py", + "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "", - "start_line": 58, - "end_line": 58, - "excerpt": "from embodichain.lab.sim.sensors import BaseSensor, SensorCfg" + "start_line": 102, + "end_line": 105, + "excerpt": "from embodichain.lab.task_program.language.schema import (\n TaskProgramCfg,\n TaskProgramIntegrationCfg,\n)" } ] }, { - "id": "static-imports-8031f6c0ab0b", - "source": "environment-adapter", - "target": "semantic-compiler", + "id": "static-imports-7775e33835c0", + "source": "embodied-env", + "target": "managers", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/task_program/integrations/environment.py", + "path": "embodichain/lab/gym/envs/embodied_env.py", "symbol": "", - "start_line": 48, - "end_line": 53, - "excerpt": "from embodichain.lab.task_program.compiler.lowering import (\n HandOverPoseProvider,\n RegisteredSemanticLowerer,\n RelationTargetGrounder,\n SemanticCallCompiler,\n)" + "start_line": 75, + "end_line": 82, + "excerpt": "from embodichain.lab.gym.envs.managers import (\n EventManager,\n ObservationManager,\n RewardManager,\n ActionManager,\n ActionTrace,\n DatasetManager,\n)" } ] }, @@ -6356,6 +6476,24 @@ } ] }, + { + "id": "static-imports-837f02a1a192", + "source": "embodied-env", + "target": "configuration", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/gym/envs/embodied_env.py", + "symbol": "", + "start_line": 89, + "end_line": 89, + "excerpt": "from embodichain.utils import configclass, logger" + } + ] + }, { "id": "static-imports-838e20361fa9", "source": "ppo", @@ -6374,6 +6512,24 @@ } ] }, + { + "id": "static-imports-84f40c9ef00a", + "source": "environment-adapter", + "target": "semantic-executor", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/environment.py", + "symbol": "", + "start_line": 75, + "end_line": 75, + "excerpt": "from embodichain.lab.task_program.runtime.executor import SemanticCallExecutor" + } + ] + }, { "id": "static-imports-853d19def5ce", "source": "apg", @@ -6428,42 +6584,6 @@ } ] }, - { - "id": "static-imports-8b4a4ae9bfdb", - "source": "environment-adapter", - "target": "effect-evidence", - "relation": "imports", - "description": "The selected source module declares an absolute import associated with this target.", - "provenance": "static-extracted", - "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", - "evidence": [ - { - "path": "embodichain/lab/task_program/integrations/environment.py", - "symbol": "", - "start_line": 55, - "end_line": 59, - "excerpt": "from embodichain.lab.task_program.semantics.evidence import (\n EffectEvidenceCollector,\n EffectEvidenceProvider,\n EffectEvidenceProviderRegistry,\n)" - } - ] - }, - { - "id": "static-imports-8c8643ce3c4d", - "source": "environment-adapter", - "target": "task-program-config", - "relation": "imports", - "description": "The selected source module declares an absolute import associated with this target.", - "provenance": "static-extracted", - "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", - "evidence": [ - { - "path": "embodichain/lab/task_program/integrations/environment.py", - "symbol": "", - "start_line": 93, - "end_line": 96, - "excerpt": "from embodichain.lab.task_program.language.schema import (\n TaskProgramCfg,\n TaskProgramIntegrationCfg,\n)" - } - ] - }, { "id": "static-imports-8d99eef544be", "source": "simulation-manager", @@ -6573,38 +6693,38 @@ ] }, { - "id": "static-imports-9930294a9a0a", - "source": "embodied-env", - "target": "managers", + "id": "static-imports-960357ce12e9", + "source": "env-launcher", + "target": "task-discovery", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/gym/envs/embodied_env.py", + "path": "embodichain/lab/scripts/run_env.py", "symbol": "", - "start_line": 75, - "end_line": 81, - "excerpt": "from embodichain.lab.gym.envs.managers import (\n EventManager,\n ObservationManager,\n RewardManager,\n ActionManager,\n DatasetManager,\n)" + "start_line": 48, + "end_line": 51, + "excerpt": "from embodichain.lab.gym.utils.registration import (\n discover_task_packages,\n execute_init_hooks,\n)" } ] }, { - "id": "static-imports-998c84daa8f4", - "source": "embodied-env", - "target": "task-discovery", + "id": "static-imports-968f7fc35249", + "source": "env-launcher", + "target": "config-loader", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/gym/envs/embodied_env.py", + "path": "embodichain/lab/scripts/run_env.py", "symbol": "", - "start_line": 82, - "end_line": 82, - "excerpt": "from embodichain.lab.gym.utils.registration import register_env" + "start_line": 42, + "end_line": 47, + "excerpt": "from embodichain.lab.gym.utils.gym_utils import (\n add_env_launcher_args_to_parser,\n build_env_cfg_from_args,\n load_trajectory,\n _load_expansion_declaration,\n)" } ] }, @@ -6645,20 +6765,20 @@ ] }, { - "id": "static-imports-9e7383aa2316", - "source": "embodied-env", - "target": "configuration", + "id": "static-imports-a225c2b26ae1", + "source": "environment-adapter", + "target": "skill-profile", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/gym/envs/embodied_env.py", + "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "", - "start_line": 88, - "end_line": 88, - "excerpt": "from embodichain.utils import configclass, logger" + "start_line": 70, + "end_line": 74, + "excerpt": "from embodichain.lab.task_program.semantics.profiles import (\n ResourceEndpoint,\n ResourceEndpointAdapter,\n RobotSkillProfile,\n)" } ] }, @@ -6735,9 +6855,27 @@ ] }, { - "id": "static-imports-a874b77ce380", + "id": "static-imports-a71cf24b0e48", + "source": "simulation-factory", + "target": "skill-profile", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/simulation/environment.py", + "symbol": "", + "start_line": 76, + "end_line": 76, + "excerpt": "from embodichain.lab.task_program.semantics.profiles import RobotSkillProfile" + } + ] + }, + { + "id": "static-imports-a752eb077481", "source": "environment-adapter", - "target": "scene-registry", + "target": "gym-bridge", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", @@ -6746,9 +6884,9 @@ { "path": "embodichain/lab/task_program/integrations/environment.py", "symbol": "", - "start_line": 73, - "end_line": 73, - "excerpt": "from embodichain.lab.task_program.semantics.scene import SceneRegistry" + "start_line": 81, + "end_line": 92, + "excerpt": "from embodichain.lab.gym.envs.task_program.bridge import (\n TaskProgramDemoBridge,\n BufferedGymCommandSink,\n CurrentQposProvider,\n TaskProgramBridgeError,\n EnvironmentStepClock,\n JointPositionGymTransportEncoder,\n RuntimeCommandFrameEncoder,\n RuntimeTransportActionEncoder,\n SegmentPostPolicyPort,\n SegmentValidatorPort,\n)" } ] }, @@ -6825,56 +6963,56 @@ ] }, { - "id": "static-imports-b49dc0306bef", - "source": "simulation-factory", - "target": "parallel-execution", + "id": "static-imports-b7c58479a36f", + "source": "rl-routing", + "target": "differentiable-trainer", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/task_program/integrations/simulation/environment.py", + "path": "embodichain/learning/rl/train.py", "symbol": "", - "start_line": 65, - "end_line": 67, - "excerpt": "from embodichain.lab.task_program.runtime.parallel_executor import (\n ParallelCommandSafetyValidator,\n)" + "start_line": 41, + "end_line": 44, + "excerpt": "from embodichain.learning.rl.differentiable_trainer import (\n DifferentiableTrainer,\n DifferentiableTrainerCfg,\n)" } ] }, { - "id": "static-imports-b7b3ac4916aa", - "source": "environment-adapter", - "target": "gym-bridge", + "id": "static-imports-b922bba59573", + "source": "embodied-env", + "target": "config-loader", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/task_program/integrations/environment.py", + "path": "embodichain/lab/gym/envs/embodied_env.py", "symbol": "", - "start_line": 75, - "end_line": 86, - "excerpt": "from embodichain.lab.gym.envs.task_program.bridge import (\n TaskProgramDemoBridge,\n BufferedGymCommandSink,\n CurrentQposProvider,\n TaskProgramBridgeError,\n EnvironmentStepClock,\n JointPositionGymTransportEncoder,\n RuntimeCommandFrameEncoder,\n RuntimeTransportActionEncoder,\n SegmentPostPolicyPort,\n SegmentValidatorPort,\n)" + "start_line": 84, + "end_line": 87, + "excerpt": "from embodichain.lab.gym.utils.gym_utils import (\n build_trajectory_buffer,\n init_rollout_buffer_from_gym_space,\n)" } ] }, { - "id": "static-imports-b7c58479a36f", - "source": "rl-routing", - "target": "differentiable-trainer", + "id": "static-imports-ba6fbe093f12", + "source": "simulation-factory", + "target": "step-clock", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/learning/rl/train.py", + "path": "embodichain/lab/task_program/integrations/simulation/environment.py", "symbol": "", - "start_line": 41, - "end_line": 44, - "excerpt": "from embodichain.learning.rl.differentiable_trainer import (\n DifferentiableTrainer,\n DifferentiableTrainerCfg,\n)" + "start_line": 84, + "end_line": 87, + "excerpt": "from embodichain.lab.gym.envs.task_program.bridge import (\n EnvironmentStepClock,\n GymPlanningObservationProvider,\n)" } ] }, @@ -6932,6 +7070,24 @@ } ] }, + { + "id": "static-imports-bf1afb4279c8", + "source": "scene-expansion-integration", + "target": "embodied-env", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration under if TYPE_CHECKING. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/scene_expansion.py", + "symbol": "", + "start_line": 37, + "end_line": 37, + "excerpt": " from embodichain.lab.gym.envs.embodied_env import EmbodiedEnv" + } + ] + }, { "id": "static-imports-c80e3323f6a8", "source": "parallel-execution", @@ -7005,20 +7161,20 @@ ] }, { - "id": "static-imports-ce7495ee8371", - "source": "environment-adapter", - "target": "command-sink", + "id": "static-imports-d19e53583688", + "source": "articulations", + "target": "object-backends", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/task_program/integrations/environment.py", + "path": "embodichain/lab/sim/objects/articulation.py", "symbol": "", - "start_line": 75, - "end_line": 86, - "excerpt": "from embodichain.lab.gym.envs.task_program.bridge import (\n TaskProgramDemoBridge,\n BufferedGymCommandSink,\n CurrentQposProvider,\n TaskProgramBridgeError,\n EnvironmentStepClock,\n JointPositionGymTransportEncoder,\n RuntimeCommandFrameEncoder,\n RuntimeTransportActionEncoder,\n SegmentPostPolicyPort,\n SegmentValidatorPort,\n)" + "start_line": 57, + "end_line": 57, + "excerpt": "from embodichain.lab.sim.objects.backends import SceneArticulationView" } ] }, @@ -7040,6 +7196,24 @@ } ] }, + { + "id": "static-imports-d76fb921e4d7", + "source": "environment-adapter", + "target": "effect-evidence", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/environment.py", + "symbol": "", + "start_line": 58, + "end_line": 62, + "excerpt": "from embodichain.lab.task_program.semantics.evidence import (\n EffectEvidenceCollector,\n EffectEvidenceProvider,\n EffectEvidenceProviderRegistry,\n)" + } + ] + }, { "id": "static-imports-d9c602221a0e", "source": "skill-profile", @@ -7076,24 +7250,6 @@ } ] }, - { - "id": "static-imports-ddc442f99a40", - "source": "embodied-env", - "target": "gym-bridge", - "relation": "imports", - "description": "The selected source module declares an absolute import associated with this target.", - "provenance": "static-extracted", - "scope": "Module-level declaration under if TYPE_CHECKING. Not a runtime call or proof that every class in the file uses the imported symbol.", - "evidence": [ - { - "path": "embodichain/lab/gym/envs/embodied_env.py", - "symbol": "", - "start_line": 98, - "end_line": 98, - "excerpt": " from embodichain.lab.gym.envs.task_program.bridge import TaskProgramDemoBridge" - } - ] - }, { "id": "static-imports-e1d222cb5a02", "source": "base-env", @@ -7130,24 +7286,6 @@ } ] }, - { - "id": "static-imports-e2bc684c7e4c", - "source": "environment-adapter", - "target": "execution-runner", - "relation": "imports", - "description": "The selected source module declares an absolute import associated with this target.", - "provenance": "static-extracted", - "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", - "evidence": [ - { - "path": "embodichain/lab/task_program/integrations/environment.py", - "symbol": "", - "start_line": 35, - "end_line": 38, - "excerpt": "from embodichain.lab.sim.atomic_actions.runner import (\n ExecutionRunnerCfg,\n ObservationProvider,\n)" - } - ] - }, { "id": "static-imports-e78e1b0a7280", "source": "gizmo", @@ -7184,24 +7322,6 @@ } ] }, - { - "id": "static-imports-eb84e6a51ed6", - "source": "env-launcher", - "target": "task-discovery", - "relation": "imports", - "description": "The selected source module declares an absolute import associated with this target.", - "provenance": "static-extracted", - "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", - "evidence": [ - { - "path": "embodichain/lab/scripts/run_env.py", - "symbol": "", - "start_line": 45, - "end_line": 48, - "excerpt": "from embodichain.lab.gym.utils.registration import (\n discover_task_packages,\n execute_init_hooks,\n)" - } - ] - }, { "id": "static-imports-ebc66b0f9d4a", "source": "grasp-backend", @@ -7221,27 +7341,27 @@ ] }, { - "id": "static-imports-ec4453f4d95a", - "source": "simulation-factory", - "target": "step-clock", + "id": "static-imports-f67e0a6b3152", + "source": "gizmo", + "target": "batch-entity", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", "evidence": [ { - "path": "embodichain/lab/task_program/integrations/simulation/environment.py", + "path": "embodichain/lab/sim/objects/gizmo.py", "symbol": "", - "start_line": 74, - "end_line": 77, - "excerpt": "from embodichain.lab.gym.envs.task_program.bridge import (\n EnvironmentStepClock,\n GymPlanningObservationProvider,\n)" + "start_line": 35, + "end_line": 35, + "excerpt": "from embodichain.lab.sim.common import BatchEntity" } ] }, { - "id": "static-imports-ec9087e04d68", + "id": "static-imports-f6de2a8900d6", "source": "simulation-factory", - "target": "semantic-compiler", + "target": "effect-evidence", "relation": "imports", "description": "The selected source module declares an absolute import associated with this target.", "provenance": "static-extracted", @@ -7250,45 +7370,9 @@ { "path": "embodichain/lab/task_program/integrations/simulation/environment.py", "symbol": "", - "start_line": 56, - "end_line": 58, - "excerpt": "from embodichain.lab.task_program.compiler.lowering import (\n RegisteredSemanticLowerer,\n)" - } - ] - }, - { - "id": "static-imports-f385bb6c0702", - "source": "scene-editing", - "target": "scene-layout", - "relation": "imports", - "description": "The selected source module declares an absolute import associated with this target.", - "provenance": "static-extracted", - "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", - "evidence": [ - { - "path": "embodichain/gen_sim/scene_engine/pipeline/edit.py", - "symbol": "", - "start_line": 45, - "end_line": 47, - "excerpt": "from embodichain.gen_sim.scene_engine.pipeline.editing.scene_edit_layout_generation import (\n edit_layout,\n)" - } - ] - }, - { - "id": "static-imports-f67e0a6b3152", - "source": "gizmo", - "target": "batch-entity", - "relation": "imports", - "description": "The selected source module declares an absolute import associated with this target.", - "provenance": "static-extracted", - "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", - "evidence": [ - { - "path": "embodichain/lab/sim/objects/gizmo.py", - "symbol": "", - "start_line": 35, - "end_line": 35, - "excerpt": "from embodichain.lab.sim.common import BatchEntity" + "start_line": 67, + "end_line": 72, + "excerpt": "from embodichain.lab.task_program.semantics.evidence import (\n ControlPartRobotEvidenceSource,\n ControlPartSimulationEvidenceProvider,\n EffectEvidenceProvider,\n SceneArticulationEvidenceProvider,\n)" } ] }, @@ -7364,6 +7448,42 @@ } ] }, + { + "id": "static-imports-fd54613e0ad9", + "source": "environment-adapter", + "target": "scene-registry", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/task_program/integrations/environment.py", + "symbol": "", + "start_line": 79, + "end_line": 79, + "excerpt": "from embodichain.lab.task_program.semantics.scene import SceneRegistry" + } + ] + }, + { + "id": "static-imports-fd7e0cdcb271", + "source": "embodied-env", + "target": "task-discovery", + "relation": "imports", + "description": "The selected source module declares an absolute import associated with this target.", + "provenance": "static-extracted", + "scope": "Module-level declaration. Not a runtime call or proof that every class in the file uses the imported symbol.", + "evidence": [ + { + "path": "embodichain/lab/gym/envs/embodied_env.py", + "symbol": "", + "start_line": 83, + "end_line": 83, + "excerpt": "from embodichain.lab.gym.utils.registration import register_env" + } + ] + }, { "id": "static-imports-ff6295f38963", "source": "tasks", @@ -7551,6 +7671,7 @@ "semantic-executor", "environment-adapter", "gym-bridge", + "scene-expansion-integration", "mllm", "atomic-engine", "motion-generator", @@ -7559,6 +7680,7 @@ "workspace", "augmentation", "simulation-manager", + "scene-expansion", "robot", "sensors", "physics", @@ -7691,13 +7813,15 @@ "rl-algorithms-holds-ppo", "rl-algorithms-holds-grpo", "rl-algorithms-holds-apg", + "scene-attempt-imports-scene-contracts", + "scene-attempt-calls-task-program-segments", "static-imports-6887f760c2d8", "static-imports-804702892bff", "static-imports-ccdd4adf0e59", "static-imports-b09fe3ed8e44", "static-imports-28b0ba416298", "static-imports-be091d31f810", - "static-imports-f385bb6c0702", + "static-imports-218cb7b6a16d", "static-imports-6aa818d63f6a", "static-imports-8fb0e4d5119f", "static-imports-a2ed85aaf8e4", @@ -7707,15 +7831,15 @@ "static-imports-cc506a698f2f", "static-imports-e1d222cb5a02", "static-imports-009eb229e628", - "static-imports-6efc6c8fee5c", - "static-imports-9930294a9a0a", - "static-imports-998c84daa8f4", - "static-imports-36b2619c635f", - "static-imports-9e7383aa2316", - "static-imports-ddc442f99a40", + "static-imports-32e084e6bfb3", + "static-imports-7775e33835c0", + "static-imports-fd7e0cdcb271", + "static-imports-b922bba59573", + "static-imports-837f02a1a192", + "static-imports-5062dce2cb84", "static-imports-3c5c9b9d4364", - "static-imports-6b4f407022e8", - "static-imports-eb84e6a51ed6", + "static-imports-968f7fc35249", + "static-imports-960357ce12e9", "static-imports-36d8a240dfd7", "static-imports-ab15f87df47f", "static-imports-bc79368679c6", @@ -7734,9 +7858,10 @@ "static-imports-a32fb8db0ae1", "static-imports-bba2d7266a0c", "static-imports-3d4c7a13ce4e", - "static-imports-8031f6c0ab0b", - "static-imports-1d1929c1f98e", - "static-imports-b7b3ac4916aa", + "static-imports-14722eceaa11", + "static-imports-84f40c9ef00a", + "static-imports-a752eb077481", + "static-imports-bf1afb4279c8", "static-imports-237cf7f458f9", "static-imports-853d19def5ce", "static-imports-94a1dd303d9e", @@ -7785,6 +7910,7 @@ "semantic-executor", "environment-adapter", "gym-bridge", + "scene-expansion-integration", "mllm" ] }, @@ -7805,6 +7931,7 @@ "label": "Simulation world", "node_ids": [ "simulation-manager", + "scene-expansion", "robot", "sensors", "physics", @@ -7909,6 +8036,7 @@ "mllm", "program-input", "scene-registry", + "scene-expansion", "skill-profile", "integration-catalog", "task-program-config", @@ -7924,6 +8052,7 @@ "parallel-execution", "effect-evidence", "gym-bridge", + "scene-expansion-integration", "command-sink", "step-clock", "embodied-env", @@ -7959,10 +8088,12 @@ "env-calls-add-robot", "mllm-imports-program-input", "mllm-calls-environment-adapter", + "scene-attempt-imports-scene-contracts", + "scene-attempt-calls-task-program-segments", "static-imports-490e86539325", "static-imports-6887f760c2d8", "static-imports-5a790441a2e1", - "static-imports-ddc442f99a40", + "static-imports-5062dce2cb84", "static-imports-36d8a240dfd7", "static-imports-a6906547dfc6", "static-imports-2b8a0b73365e", @@ -7970,25 +8101,26 @@ "static-imports-e28cb4f07587", "static-imports-2439dce9f223", "static-imports-3d4c7a13ce4e", - "static-imports-e2bc684c7e4c", - "static-imports-8031f6c0ab0b", - "static-imports-8b4a4ae9bfdb", - "static-imports-662f359eaf35", - "static-imports-374116b69d45", - "static-imports-1d1929c1f98e", - "static-imports-a874b77ce380", - "static-imports-b7b3ac4916aa", - "static-imports-ce7495ee8371", - "static-imports-5ce7e9c376fd", - "static-imports-8c8643ce3c4d", + "static-imports-62adb31d7b9c", + "static-imports-14722eceaa11", + "static-imports-d76fb921e4d7", + "static-imports-34a290f7867e", + "static-imports-a225c2b26ae1", + "static-imports-84f40c9ef00a", + "static-imports-fd54613e0ad9", + "static-imports-a752eb077481", + "static-imports-304018fdd2a7", + "static-imports-635bb46d2393", + "static-imports-70d934a6079b", + "static-imports-bf1afb4279c8", "static-imports-25644243d165", - "static-imports-ec9087e04d68", - "static-imports-16d95b2b8cb0", - "static-imports-b49dc0306bef", - "static-imports-5eb4989e8f9a", - "static-imports-1a349a9bf733", - "static-imports-ec4453f4d95a", - "static-imports-292707ad785d", + "static-imports-0f0d7a478bf8", + "static-imports-f6de2a8900d6", + "static-imports-416b1c69c882", + "static-imports-a71cf24b0e48", + "static-imports-2549d7070bdd", + "static-imports-ba6fbe093f12", + "static-imports-1bb109404700", "static-imports-237cf7f458f9", "static-imports-8f7173da3812", "static-imports-41cc9bb00185", @@ -8007,6 +8139,7 @@ "mllm", "program-input", "scene-registry", + "scene-expansion", "skill-profile", "integration-catalog" ] @@ -8052,6 +8185,7 @@ "label": "Gym integration & simulation", "node_ids": [ "gym-bridge", + "scene-expansion-integration", "command-sink", "step-clock", "embodied-env", @@ -8073,6 +8207,7 @@ "robot", "articulations", "rigid-objects", + "scene-expansion", "rigid-object-groups", "deformables", "surface-deformables", @@ -8095,6 +8230,7 @@ "differentiable-env", "autograd-bridge", "configuration", + "scene-expansion-integration", "data-assets", "event-manager", "randomization", @@ -8131,6 +8267,7 @@ "articulations-inherits-batch-entity", "deformables-inherits-batch-entity", "lights-inherits-batch-entity", + "scene-attempt-imports-scene-contracts", "static-imports-547bae782cdb", "static-imports-36d8a240dfd7", "static-imports-5529b5ed4bec", @@ -8139,8 +8276,8 @@ "static-imports-f6f4ac515679", "static-imports-9bc131d4e099", "static-imports-688b3daedb5b", - "static-imports-22b538406c16", - "static-imports-1c2288600190", + "static-imports-19c0e59c86ea", + "static-imports-d19e53583688", "static-imports-e80669c4fea7", "static-imports-0ae3233a127d", "static-imports-f67e0a6b3152", @@ -8177,6 +8314,7 @@ "robot", "articulations", "rigid-objects", + "scene-expansion", "rigid-object-groups", "deformables", "surface-deformables", @@ -8223,6 +8361,7 @@ "label": "Configuration & control", "node_ids": [ "configuration", + "scene-expansion-integration", "data-assets", "event-manager", "randomization", @@ -8295,7 +8434,7 @@ "rl-algorithms-holds-apg", "static-imports-804702892bff", "static-imports-ccdd4adf0e59", - "static-imports-9930294a9a0a", + "static-imports-7775e33835c0", "static-imports-6e4557b26941", "static-imports-a595aa28bca6", "static-imports-35d217cf64ea", @@ -8424,7 +8563,7 @@ "simready-calls-asset-parsers", "static-imports-28b0ba416298", "static-imports-be091d31f810", - "static-imports-f385bb6c0702", + "static-imports-218cb7b6a16d", "static-imports-6aa818d63f6a", "static-imports-8fb0e4d5119f", "static-imports-a2ed85aaf8e4", diff --git a/docs/architecture/generated/summary.md b/docs/architecture/generated/summary.md index 4a4dc271f..f72c7d4c1 100644 --- a/docs/architecture/generated/summary.md +++ b/docs/architecture/generated/summary.md @@ -1,133 +1,135 @@ # Architecture overview -Source revision: `abd754a3d86e82a6838e3062adda001f8b097007` · package 0.2.4 +Source revision: `61b2fbee9682ddeb7b0bc09f64c018509d18a01b` · package 0.3.0 Curated static relationships; not a complete dependency graph or runtime trace. | Module | Responsibility | Evidence and documentation | | --- | --- | --- | -| CLI | Dispatches commands and lazily loads task discovery, environment, and training entry points. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/cli/main.py#L17-L17) · [CLI reference](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/guides/cli.md) | -| Environment launcher | Runs task discovery, configuration loading, and Gym environment creation. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/scripts/run_env.py#L994-L994) · [Environment launch guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/guides/run_env.md) | -| Task discovery | Imports installed task packages through entry points to populate the environment registry. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/utils/registration.py#L430-L430) · [Task discovery and supported tasks](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/resources/task/index.rst) | -| Official tasks | Registers official task entry points and organises reusable robot tasks. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain_tasks/embodichain_tasks/__init__.py#L17-L23) · [Supported tasks](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/resources/task/index.rst) | -| Config loader | Loads deployment files, resolves components, and assembles the typed environment configuration. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/utils/gym_utils.py#L1211-L1211) · [Configuration guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/guides/configuration.md) | -| Component composition | Resolves environment and embodiment components into runnable Gym configurations. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/utils/_component_composition.py#L17-L17) · [Configuration guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/guides/configuration.md) | -| EmbodiedEnv | Assembles scenes, managers, and demonstrations with optional Task Program integration. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/embodied_env.py#L284-L284) · [EmbodiedEnv documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/gym/env.md) | -| BaseEnv | Owns the Gym loop, simulation initialisation, and environment step lifecycle. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/base_env.py#L105-L105) · [BaseEnv documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/gym/env.md) | -| Managers | Organises observations, actions, rewards, events, and data recording within the environment lifecycle. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/managers/__init__.py#L17-L20) · [Gym managers and environment lifecycle](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/gym/index.rst) | -| Configuration utilities | Defines typed configuration copying, serialization, and required-field validation. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/utils/configclass.py#L14-L14) · [Configuration utilities documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.utils.rst) | -| Domain randomization | Event functors randomize physical, visual, spatial, and geometric properties. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/managers/randomization/__init__.py#L17-L20) · [Domain randomization documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.managers.rst) | -| DifferentiableEnv | Preserves the Gym lifecycle while bridging task-defined kinematics to autograd. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/differentiable_env.py#L46-L46) · [DifferentiableEnv documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.rst) | -| EventManager | Dispatches configured event functors with lifecycle scheduling and scoped seeds. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/managers/event_manager.py#L97-L97) · [EventManager documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.managers.rst) | -| TaskProgramCompiler | Compiles task configuration into an analysable program using a static scene manifest. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/compiler/program.py#L1179-L1179) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/index.md) · [TaskProgramCompiler documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.task_program.compiler.rst) | -| SemanticCallCompiler | Analyses and lowers Semantic Calls against the bound scene and skill integration. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/compiler/lowering.py#L767-L767) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/index.md) · [SemanticCallCompiler documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.task_program.compiler.rst) | -| SemanticCallExecutor | Lowers and executes semantic calls, creating an execution session for each call. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/runtime/executor.py#L216-L216) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/index.md) · [SemanticCallExecutor documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.task_program.runtime.rst) | -| TaskProgramEnvironmentAdapter | Validates integration choices and assembles the semantic runtime and Gym bridge. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/integrations/environment.py#L295-L295) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/index.md) · [TaskProgramEnvironmentAdapter documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.rst) | -| TaskProgramDemoBridge | Converts program segments into lazy Gym demonstrations with a shared runtime, buffer, and clock. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/task_program/bridge.py#L951-L951) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/index.md) · [TaskProgramDemoBridge documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.task_program.rst) | -| MLLM frontend | Decodes untrusted model JSON and compiles it through the existing Task Program adapter. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/agents/mllm/task_program.py#L17-L17) · [MLLM frontend documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/index.md) | -| AtomicActionEngine | Manages planning resources and atomic skills to create executable action sessions. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/atomic_actions/engine.py#L46-L46) · [Atomic Skills guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/sim/atomic_actions/index.md) · [AtomicActionEngine documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.atomic_actions.rst) | -| MotionGenerator | Composes planning backends behind a unified robot motion generation interface. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/motion/motion_generator.py#L191-L191) · [Motion generation guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/sim/motion/motion_generator.md) · [MotionGenerator documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.motion.motion_generator.rst) | -| Planners | Provides motion planning backends that generate constrained robot trajectories. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/motion/planners/__init__.py#L17-L22) · [Motion planner guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/sim/motion/planners/index.rst) | -| Solvers | Provides inverse kinematics backends and stateful robot solving capabilities. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/motion/solvers/__init__.py#L17-L20) · [Kinematic solver guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/sim/motion/solvers/index.rst) | -| Robot workspace | Loads reachability caches and samples robot joint configurations and poses. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/motion/workspace/runtime.py#L53-L53) · [Robot workspace documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/workspace_analyzer/runtime.md) | -| Trajectory augmentation | Tracks trajectory candidates, expansion budgets, coverage, and commit receipts. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/motion/expansion/session.py#L108-L108) · [Trajectory augmentation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.motion.expansion.rst) | -| SimulationManager | Manages the world, robots, sensors, physics, and visualisation lifecycle. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/sim_manager.py#L490-L490) · [SimulationManager documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/sim/sim_manager.md) | -| Robot | Represents a simulated robot with control parts, joint state, and kinematics. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/objects/robot.py#L17-L17) · [Robots and articulations](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/sim/sim_articulation.md) | -| Sensors | Provides camera, stereo, and contact observations for the environment. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/sensors/__init__.py#L17-L20) · [Sensor guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/sim/sensors/index.rst) | -| Physics backends | Constructs the configured physics backend and connects simulation state to physics solving. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/physics/__init__.py#L16-L21) · [Physics backend guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/sim/sim_manager/physics/index.md) | -| Browser visualization | Publishes simulation snapshots and interactive controls through a browser runtime. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/visualization/runtime.py#L17-L17) · [Browser visualization guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/sim/viser_visualization.md) | -| Rigid objects | Represents rigid scene objects and their physical state. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/objects/rigid_object.py#L352-L352) · [Rigid objects documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | -| Rigid object groups | Groups multiple rigid bodies into a batched scene entity. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/objects/rigid_object_group.py#L187-L187) · [Rigid object group guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/sim/sim_rigid_object_group.md) · [Object API reference](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | -| Articulations | Represents jointed scene entities and articulated state. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/objects/articulation.py#L694-L694) · [Articulations documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | -| Deformable objects | Represents Newton-backed deformable particle-set entities. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/objects/deformable/base.py#L49-L49) · [Deformable objects documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | -| Lights | Controls light color, intensity, falloff, and pose through a batched facade. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/objects/light.py#L31-L31) · [Lights documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | -| Rigid constraints | Wraps per-arena fixed constraints between two rigid objects. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/objects/constraint.py#L36-L36) · [Rigid constraints documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | -| Gizmo | Provides native robot IK and browser manipulation controls for simulation targets. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/objects/gizmo.py#L458-L458) · [Gizmo documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | -| Compute | Shared numerical algorithms for kinematics, trajectories, geometry, and images. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/compute/__init__.py#L17-L21) · [Compute API reference](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.compute.rst) | -| Trajectory computation | Shared interpolation, resampling, timing, and trajectory warping algorithms. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/compute/trajectory/__init__.py#L17-L20) · [Trajectory computation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.compute.rst) | -| Kinematics computation | Shared numerical kinematics and manipulability algorithms. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/compute/kinematics/__init__.py#L17-L22) · [Kinematics computation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.compute.rst) | -| DatasetManager | Coordinates dataset functors for frame collection, saving, and finalization. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/managers/dataset_manager.py#L39-L39) · [DatasetManager documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/gym/dataset_functors.md) | -| LeRobot recording | Persists demonstrations and metadata in LeRobot datasets with optional depth sidecars. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/managers/datasets.py#L101-L101) · [LeRobot recording documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/gym/dataset_functors.md) | -| Async recording | Moves durable dataset writes to one background worker. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/managers/async_datasets.py#L55-L55) · [Async recording documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/gym/dataset_functors.md) | -| Depth sidecar writer | Coordinates depth-video files and metadata alongside recorded demonstrations. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/data_pipeline/depth_video/writer.py#L226-L226) · [Depth video documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.data_pipeline.depth_video.rst) | -| Depth sidecar reader | Loads depth metadata and decodes episode/sensor depth video frames. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/data_pipeline/depth_video/reader.py#L17-L23) · [Depth sidecar reader documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.data_pipeline.depth_video.rst) | -| Depth quantization | Converts depth values to and from the sidecar video representation. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/data_pipeline/depth_video/depth_utils.py#L24-L35) · [Depth quantization documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.data_pipeline.depth_video.rst) | -| OnlineDataEngine | Online data engine managing workers and their lifecycle. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/data_pipeline/engine/data.py#L17-L17) · [Online data streaming guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/online_data.md) | -| OnlineDataset | Exposes live engine samples as an infinite PyTorch IterableDataset. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/data_pipeline/datasets/online_data.py#L32-L32) · [OnlineDataset documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.data_pipeline.datasets.rst) | -| Chunk samplers | Selects fixed-shape time windows using configurable chunk-length distributions. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/data_pipeline/datasets/sampler.py#L17-L17) · [Chunk samplers documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.data_pipeline.datasets.rst) | -| Data assets | Resolves asset paths and coordinates registered dataset downloads and caches. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/data/dataset.py#L17-L17) · [Data assets documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/resources/assets.md) | -| Scene generation | Generates scene state from input images for scene asset workflows. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/gen_sim/scene_engine/pipeline/generate.py#L17-L17) · [Scene Engine guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/generative_sim/scene_engine.md) | -| Scene understanding | Interprets an image, segments objects, and initializes a validated support graph. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/gen_sim/scene_engine/pipeline/generation/scene_understanding.py#L194-L194) · [Scene Engine guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/generative_sim/scene_engine.md) | -| Asset generation & refinement | Generates geometry and articulated assets, then refines scene placement. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/gen_sim/scene_engine/pipeline/generation/scene_generation.py#L94-L94) · [Scene Engine guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/generative_sim/scene_engine.md) | -| Scene asset preparation | Prepares generated Scene Engine geometry for layout and export. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/gen_sim/scene_engine/pipeline/utils/simready_processor.py#L90-L90) · [Scene Engine guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/generative_sim/scene_engine.md) | -| Scene editing | Edits an existing portable scene using a typed edit plan. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/gen_sim/scene_engine/pipeline/edit.py#L17-L17) · [Scene editing documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/generative_sim/scene_engine.md) | -| Scene edit layout | Reconstructs scene placement against an edited goal graph. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/gen_sim/scene_engine/pipeline/editing/scene_edit_layout_generation.py#L32-L32) · [Scene editing documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/generative_sim/scene_engine.md) | -| Scene import | Reconstructs an editable scene and graph from a portable scene export. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/gen_sim/scene_engine/pipeline/utils/scene_importer.py#L50-L50) · [Scene export documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/generative_sim/scene_engine.md) | -| Scene export | Writes portable scene metadata and assets for downstream simulation preparation. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/gen_sim/scene_engine/pipeline/utils/scene_exporter.py#L43-L43) · [Scene export documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/generative_sim/scene_engine.md) | -| SimReady pipeline | Ingests raw assets through parsing and conversion stages into SimReady outputs. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/gen_sim/simready_pipeline/pipeline/ingest.py#L17-L17) · [SimReady pipeline documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/generative_sim/simready_pipeline.md) | -| Asset parser stages | Dispatches inspector, geometry, physics, USD, and internal asset parsers. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/gen_sim/simready_pipeline/parser/base.py#L46-L46) · [SimReady pipeline documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/generative_sim/simready_pipeline.md) | -| GenSim UI sessions | Tracks one replaceable subprocess per Gradio session. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/gen_sim/gradio_ui/app_processes.py#L109-L109) · [Scene Engine guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/generative_sim/scene_engine.md) | -| Training assembly | Builds environments, policies, algorithms, and the compatible trainer from configuration. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/train.py#L17-L17) · [Training assembly documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/rl/train_script.md) | -| RL algorithms | Registers PPO, GRPO, and APG with their rollout contracts. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/algo/__init__.py#L17-L17) · [RL algorithms documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/rl/algorithm.md) | -| PPO | Optimizes an actor-critic policy from standard TensorDict rollouts. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/algo/ppo.py#L51-L51) · [PPO documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.learning.rl.algo.rst) | -| GRPO | Optimizes policies with group-relative advantages from standard rollouts. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/algo/grpo.py#L47-L47) · [GRPO documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.learning.rl.algo.rst) | -| APG | Optimizes policy parameters through differentiable rollout rewards. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/algo/apg.py#L103-L103) · [APG documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.learning.rl.algo.rst) | -| Policy models | Defines policy action inference and model interfaces. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/models/policy.py#L35-L35) · [Policy models documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/rl/models.md) | -| ActorCritic | Provides Gaussian actions and value estimates with separate actor and critic inputs. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/models/actor_critic.py#L34-L34) · [ActorCritic documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.learning.rl.models.rst) | -| ActorOnly | Provides Gaussian actions without a learned value network. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/models/actor_only.py#L31-L31) · [ActorOnly documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.learning.rl.models.rst) | -| Learning environments | Defines lightweight vector-environment protocols and factory registration. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/env.py#L17-L17) · [Learning environments documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.learning.rl.rst) | -| Standard trainer | Coordinates standard rollout collection, algorithm updates, evaluation, and checkpoints. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/utils/trainer.py#L17-L17) · [Reinforcement learning overview](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/rl/index.rst) | -| SyncCollector | Collects standard vector-environment rollouts into the training buffer. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/collector/sync_collector.py#L33-L33) · [SyncCollector documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.learning.rl.collector.rst) | -| Rollout buffer | Stores standard rollout transitions and bootstrap state in TensorDict buffers. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/buffer/standard_buffer.py#L27-L27) · [Rollout buffer documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/rl/buffer.md) | -| DifferentiableTrainer | Coordinates graph-connected rollouts and analytic policy-gradient updates. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/differentiable_trainer.py#L78-L78) · [DifferentiableTrainer documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.learning.rl.rst) | -| Differentiable collector | Collects differentiable trajectories while preserving their autograd graph. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/collector/differentiable.py#L132-L132) · [Differentiable collector documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.learning.rl.collector.rst) | -| Running observation normalization | Normalizes continuous observation fields with explicit running-statistic updates. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/normalization.py#L29-L29) · [Running observation normalization documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.learning.rl.rst) | -| Policy evaluation | Runs trained policy evaluation in headless or viewer workflows. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/learning/rl/policy_evaluation/cli.py#L17-L17) · [Policy evaluation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.learning.rl.policy_evaluation.rst) | -| Devices | Defines start, stop, and controller-state interfaces for robot input devices. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/devices/device.py#L17-L17) · [Laboratory and device API reference](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.rst) | -| Device controller | Maps input-device state to robot joint commands with joint-limit handling. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/devices/device_controller.py#L29-L29) · [Device controller documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.rst) | -| GraspKit | Defines parallel-jaw gripper models and grasp-pose generation interfaces. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/graspkit/pose_generator.py#L17-L17) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/grasp_generator.rst) | -| URDF assembly | Assembles URDF components, connections, sensors, and mesh resources. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/urdf_assembly/urdf_assembly_manager.py#L78-L78) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/urdf_assembly.md) | -| Convex decomposition | Prepares convex collision meshes for URDF assets. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/acd/cli.py#L17-L17) · [Convex decomposition documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/convex_decomposition.md) | +| CLI | Dispatches commands and lazily loads task discovery, environment, and training entry points. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/cli/main.py#L17-L17) · [CLI reference](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/guides/cli.md) | +| Environment launcher | Runs task discovery, configuration loading, and Gym environment creation. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/scripts/run_env.py#L1641-L1641) · [Environment launch guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/guides/run_env.md) | +| Task discovery | Imports installed task packages through entry points to populate the environment registry. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/utils/registration.py#L430-L430) · [Task discovery and supported tasks](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/resources/task/index.rst) | +| Official tasks | Registers official task entry points and organises reusable robot tasks. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain_tasks/embodichain_tasks/__init__.py#L17-L23) · [Supported tasks](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/resources/task/index.rst) | +| Config loader | Loads deployment files, resolves components, and assembles the typed environment configuration. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/utils/gym_utils.py#L1469-L1469) · [Configuration guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/guides/configuration.md) | +| Component composition | Resolves environment and embodiment components into runnable Gym configurations. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/utils/_component_composition.py#L17-L17) · [Configuration guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/guides/configuration.md) | +| EmbodiedEnv | Assembles scenes, managers, and demonstrations with optional Task Program integration. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/embodied_env.py#L299-L299) · [EmbodiedEnv documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/gym/env.md) | +| BaseEnv | Owns the Gym loop, simulation initialisation, and environment step lifecycle. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/base_env.py#L105-L105) · [BaseEnv documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/gym/env.md) | +| Managers | Organises observations, actions, rewards, events, and data recording within the environment lifecycle. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/managers/__init__.py#L17-L20) · [Gym managers and environment lifecycle](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/gym/index.rst) | +| Configuration utilities | Defines typed configuration copying, serialization, and required-field validation. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/utils/configclass.py#L14-L14) · [Configuration utilities documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.utils.rst) | +| Domain randomization | Event functors randomize physical, visual, spatial, and geometric properties. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/managers/randomization/__init__.py#L17-L20) · [Domain randomization documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.managers.rst) | +| DifferentiableEnv | Preserves the Gym lifecycle while bridging task-defined kinematics to autograd. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/differentiable_env.py#L46-L46) · [DifferentiableEnv documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.rst) | +| EventManager | Dispatches configured event functors with lifecycle scheduling and scoped seeds. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/managers/event_manager.py#L97-L97) · [EventManager documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.managers.rst) | +| TaskProgramCompiler | Compiles task configuration into an analysable program using a static scene manifest. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/compiler/program.py#L1179-L1179) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/index.md) · [TaskProgramCompiler documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.compiler.rst) | +| SemanticCallCompiler | Analyses and lowers Semantic Calls against the bound scene and skill integration. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/compiler/lowering.py#L767-L767) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/index.md) · [SemanticCallCompiler documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.compiler.rst) | +| SemanticCallExecutor | Lowers and executes semantic calls, creating an execution session for each call. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/runtime/executor.py#L225-L225) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/index.md) · [SemanticCallExecutor documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.runtime.rst) | +| TaskProgramEnvironmentAdapter | Validates integration choices and assembles the semantic runtime and Gym bridge. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/integrations/environment.py#L306-L306) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/index.md) · [TaskProgramEnvironmentAdapter documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.rst) | +| TaskProgramDemoBridge | Converts program segments into lazy Gym demonstrations with a shared runtime, buffer, and clock. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/task_program/bridge.py#L1042-L1042) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/index.md) · [TaskProgramDemoBridge documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.task_program.rst) | +| Scene expansion attempt | Prepares one scene candidate and executes a Task Program with separate measured acceptance. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/integrations/scene_expansion.py#L195-L195) · [Task Program scene preparation and measured acceptance](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.scene_expansion.rst) | +| MLLM frontend | Decodes untrusted model JSON and compiles it through the existing Task Program adapter. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/agents/mllm/task_program.py#L17-L17) · [MLLM frontend documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/index.md) | +| AtomicActionEngine | Manages planning resources and atomic skills to create executable action sessions. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/atomic_actions/engine.py#L52-L52) · [Atomic Skills guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/sim/atomic_actions/index.md) · [AtomicActionEngine documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.atomic_actions.rst) | +| MotionGenerator | Composes planning backends behind a unified robot motion generation interface. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/motion/motion_generator.py#L191-L191) · [Motion generation guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/sim/motion/motion_generator.md) · [MotionGenerator documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.motion.motion_generator.rst) | +| Planners | Provides motion planning backends that generate constrained robot trajectories. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/motion/planners/__init__.py#L17-L22) · [Motion planner guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/sim/motion/planners/index.rst) | +| Solvers | Provides inverse kinematics backends and stateful robot solving capabilities. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/motion/solvers/__init__.py#L17-L20) · [Kinematic solver guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/sim/motion/solvers/index.rst) | +| Robot workspace | Loads reachability caches and samples robot joint configurations and poses. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/motion/workspace/runtime.py#L53-L53) · [Robot workspace documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/workspace_analyzer/runtime.md) | +| Trajectory augmentation | Tracks trajectory candidates, expansion budgets, coverage, and commit receipts. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/motion/expansion/session.py#L105-L105) · [Trajectory augmentation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.motion.expansion.rst) | +| SimulationManager | Manages the world, robots, sensors, physics, and visualisation lifecycle. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/sim_manager.py#L490-L490) · [SimulationManager documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/sim/sim_manager.md) | +| Scene expansion contracts | Defines immutable arena-local scene proposals and bounded authored candidate sources. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/scene_expansion/__init__.py#L25-L27) · [Scene expansion contracts and fixed candidates](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.scene_expansion.rst) | +| Robot | Represents a simulated robot with control parts, joint state, and kinematics. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/objects/robot.py#L17-L17) · [Robots and articulations](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/sim/sim_articulation.md) | +| Sensors | Provides camera, stereo, and contact observations for the environment. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/sensors/__init__.py#L17-L20) · [Sensor guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/sim/sensors/index.rst) | +| Physics backends | Constructs the configured physics backend and connects simulation state to physics solving. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/physics/__init__.py#L16-L21) · [Physics backend guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/sim/sim_manager/physics/index.md) | +| Browser visualization | Publishes simulation snapshots and interactive controls through a browser runtime. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/visualization/runtime.py#L17-L17) · [Browser visualization guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/sim/viser_visualization.md) | +| Rigid objects | Represents rigid scene objects and their physical state. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/objects/rigid_object.py#L352-L352) · [Rigid objects documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | +| Rigid object groups | Groups multiple rigid bodies into a batched scene entity. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/objects/rigid_object_group.py#L187-L187) · [Rigid object group guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/sim/sim_rigid_object_group.md) · [Object API reference](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | +| Articulations | Represents jointed scene entities and articulated state. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/objects/articulation.py#L713-L713) · [Articulations documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | +| Deformable objects | Represents Newton-backed deformable particle-set entities. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/objects/deformable/base.py#L49-L49) · [Deformable objects documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | +| Lights | Controls light color, intensity, falloff, and pose through a batched facade. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/objects/light.py#L31-L31) · [Lights documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | +| Rigid constraints | Wraps per-arena fixed constraints between two rigid objects. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/objects/constraint.py#L36-L36) · [Rigid constraints documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | +| Gizmo | Provides native robot IK and browser manipulation controls for simulation targets. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/objects/gizmo.py#L458-L458) · [Gizmo documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | +| Compute | Shared numerical algorithms for kinematics, trajectories, geometry, and images. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/compute/__init__.py#L17-L21) · [Compute API reference](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.compute.rst) | +| Trajectory computation | Shared interpolation, resampling, timing, and trajectory warping algorithms. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/compute/trajectory/__init__.py#L17-L20) · [Trajectory computation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.compute.rst) | +| Kinematics computation | Shared numerical kinematics and manipulability algorithms. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/compute/kinematics/__init__.py#L17-L22) · [Kinematics computation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.compute.rst) | +| DatasetManager | Coordinates dataset functors for frame collection, saving, and finalization. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/managers/dataset_manager.py#L39-L39) · [DatasetManager documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/gym/dataset_functors.md) | +| LeRobot recording | Persists demonstrations and metadata in LeRobot datasets with optional depth sidecars. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/managers/datasets.py#L131-L131) · [LeRobot recording documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/gym/dataset_functors.md) | +| Async recording | Moves durable dataset writes to one background worker. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/managers/async_datasets.py#L57-L57) · [Async recording documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/gym/dataset_functors.md) | +| Depth sidecar writer | Coordinates depth-video files and metadata alongside recorded demonstrations. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/data_pipeline/depth_video/writer.py#L226-L226) · [Depth video documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.data_pipeline.depth_video.rst) | +| Depth sidecar reader | Loads depth metadata and decodes episode/sensor depth video frames. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/data_pipeline/depth_video/reader.py#L17-L23) · [Depth sidecar reader documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.data_pipeline.depth_video.rst) | +| Depth quantization | Converts depth values to and from the sidecar video representation. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/data_pipeline/depth_video/depth_utils.py#L24-L35) · [Depth quantization documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.data_pipeline.depth_video.rst) | +| OnlineDataEngine | Online data engine managing workers and their lifecycle. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/data_pipeline/engine/data.py#L17-L17) · [Online data streaming guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/online_data.md) | +| OnlineDataset | Exposes live engine samples as an infinite PyTorch IterableDataset. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/data_pipeline/datasets/online_data.py#L32-L32) · [OnlineDataset documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.data_pipeline.datasets.rst) | +| Chunk samplers | Selects fixed-shape time windows using configurable chunk-length distributions. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/data_pipeline/datasets/sampler.py#L17-L17) · [Chunk samplers documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.data_pipeline.datasets.rst) | +| Data assets | Resolves asset paths and coordinates registered dataset downloads and caches. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/data/dataset.py#L17-L17) · [Data assets documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/resources/assets.md) | +| Scene generation | Generates scene state from input images for scene asset workflows. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/gen_sim/scene_engine/pipeline/generate.py#L17-L17) · [Scene Engine guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/generative_sim/scene_engine.md) | +| Scene understanding | Interprets an image, segments objects, and initializes a validated support graph. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/gen_sim/scene_engine/pipeline/generation/scene_understanding.py#L194-L194) · [Scene Engine guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/generative_sim/scene_engine.md) | +| Asset generation & refinement | Generates geometry and articulated assets, then refines scene placement. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/gen_sim/scene_engine/pipeline/generation/scene_generation.py#L94-L94) · [Scene Engine guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/generative_sim/scene_engine.md) | +| Scene asset preparation | Prepares generated Scene Engine geometry for layout and export. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/gen_sim/scene_engine/pipeline/utils/simready_processor.py#L90-L90) · [Scene Engine guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/generative_sim/scene_engine.md) | +| Scene editing | Edits an existing portable scene using a typed edit plan. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/gen_sim/scene_engine/pipeline/edit.py#L17-L17) · [Scene editing documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/generative_sim/scene_engine.md) | +| Scene edit layout | Reconstructs scene placement against an edited goal graph. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/gen_sim/scene_engine/pipeline/editing/scene_edit_layout_generation.py#L32-L32) · [Scene editing documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/generative_sim/scene_engine.md) | +| Scene import | Reconstructs an editable scene and graph from a portable scene export. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/gen_sim/scene_engine/pipeline/utils/scene_importer.py#L50-L50) · [Scene export documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/generative_sim/scene_engine.md) | +| Scene export | Writes portable scene metadata and assets for downstream simulation preparation. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/gen_sim/scene_engine/pipeline/utils/scene_exporter.py#L43-L43) · [Scene export documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/generative_sim/scene_engine.md) | +| SimReady pipeline | Ingests raw assets through parsing and conversion stages into SimReady outputs. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/gen_sim/simready_pipeline/pipeline/ingest.py#L17-L17) · [SimReady pipeline documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/generative_sim/simready_pipeline.md) | +| Asset parser stages | Dispatches inspector, geometry, physics, USD, and internal asset parsers. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/gen_sim/simready_pipeline/parser/base.py#L46-L46) · [SimReady pipeline documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/generative_sim/simready_pipeline.md) | +| GenSim UI sessions | Tracks one replaceable subprocess per Gradio session. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/gen_sim/gradio_ui/app_processes.py#L109-L109) · [Scene Engine guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/generative_sim/scene_engine.md) | +| Training assembly | Builds environments, policies, algorithms, and the compatible trainer from configuration. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/train.py#L17-L17) · [Training assembly documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/rl/train_script.md) | +| RL algorithms | Registers PPO, GRPO, and APG with their rollout contracts. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/algo/__init__.py#L17-L17) · [RL algorithms documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/rl/algorithm.md) | +| PPO | Optimizes an actor-critic policy from standard TensorDict rollouts. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/algo/ppo.py#L51-L51) · [PPO documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.learning.rl.algo.rst) | +| GRPO | Optimizes policies with group-relative advantages from standard rollouts. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/algo/grpo.py#L47-L47) · [GRPO documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.learning.rl.algo.rst) | +| APG | Optimizes policy parameters through differentiable rollout rewards. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/algo/apg.py#L103-L103) · [APG documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.learning.rl.algo.rst) | +| Policy models | Defines policy action inference and model interfaces. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/models/policy.py#L35-L35) · [Policy models documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/rl/models.md) | +| ActorCritic | Provides Gaussian actions and value estimates with separate actor and critic inputs. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/models/actor_critic.py#L34-L34) · [ActorCritic documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.learning.rl.models.rst) | +| ActorOnly | Provides Gaussian actions without a learned value network. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/models/actor_only.py#L31-L31) · [ActorOnly documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.learning.rl.models.rst) | +| Learning environments | Defines lightweight vector-environment protocols and factory registration. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/env.py#L17-L17) · [Learning environments documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.learning.rl.rst) | +| Standard trainer | Coordinates standard rollout collection, algorithm updates, evaluation, and checkpoints. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/utils/trainer.py#L17-L17) · [Reinforcement learning overview](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/rl/index.rst) | +| SyncCollector | Collects standard vector-environment rollouts into the training buffer. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/collector/sync_collector.py#L33-L33) · [SyncCollector documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.learning.rl.collector.rst) | +| Rollout buffer | Stores standard rollout transitions and bootstrap state in TensorDict buffers. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/buffer/standard_buffer.py#L27-L27) · [Rollout buffer documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/rl/buffer.md) | +| DifferentiableTrainer | Coordinates graph-connected rollouts and analytic policy-gradient updates. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/differentiable_trainer.py#L78-L78) · [DifferentiableTrainer documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.learning.rl.rst) | +| Differentiable collector | Collects differentiable trajectories while preserving their autograd graph. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/collector/differentiable.py#L132-L132) · [Differentiable collector documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.learning.rl.collector.rst) | +| Running observation normalization | Normalizes continuous observation fields with explicit running-statistic updates. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/normalization.py#L29-L29) · [Running observation normalization documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.learning.rl.rst) | +| Policy evaluation | Runs trained policy evaluation in headless or viewer workflows. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/learning/rl/policy_evaluation/cli.py#L17-L17) · [Policy evaluation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.learning.rl.policy_evaluation.rst) | +| Devices | Defines start, stop, and controller-state interfaces for robot input devices. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/devices/device.py#L17-L17) · [Laboratory and device API reference](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.rst) | +| Device controller | Maps input-device state to robot joint commands with joint-limit handling. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/devices/device_controller.py#L29-L29) · [Device controller documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.rst) | +| GraspKit | Defines parallel-jaw gripper models and grasp-pose generation interfaces. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/graspkit/pose_generator.py#L17-L17) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/grasp_generator.rst) | +| URDF assembly | Assembles URDF components, connections, sensors, and mesh resources. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/urdf_assembly/urdf_assembly_manager.py#L78-L78) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/urdf_assembly.md) | +| Convex decomposition | Prepares convex collision meshes for URDF assets. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/acd/cli.py#L17-L17) · [Convex decomposition documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/convex_decomposition.md) | ### Additional modules in specialist views | Module | Responsibility | Evidence and documentation | | --- | --- | --- | -| Antipodal grasp generator | Generates candidate grasp poses through a reusable parallel-jaw interface. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/graspkit/pg_grasp/pose_generator.py#L172-L172) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/grasp_generator.rst) | -| Newton autograd bridge | Connects Warp tape derivatives to PyTorch action and functional-state gradients. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/diff/bridge.py#L81-L81) · [Newton autograd bridge documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.rst) | -| BatchEntity | Defines shared configuration and instance operations for batched simulation entities. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/common.py#L34-L34) · [BatchEntity documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | -| URDF collision processor | Replaces mesh collisions with convex pieces and optionally recomputes inertia. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/acd/urdf_modifider.py#L17-L17) · [Convex decomposition documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/convex_decomposition.md) | -| BufferedGymCommandSink | Validates and buffers control commands for incremental Gym demonstration consumption. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/task_program/bridge.py#L743-L743) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/index.md) · [BufferedGymCommandSink documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.task_program.rst) | -| CompiledTaskProgram | Immutable compilation result with materialized task segments and analysis views. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/compiler/program.py#L962-L962) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/index.md) · [CompiledTaskProgram documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.task_program.compiler.rst) | -| Effect evidence | Collects typed evidence from registered providers for semantic effects. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/semantics/evidence.py#L438-L438) · [Effect evidence documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.task_program.semantics.rst) | -| ExecutionRunner | Advances atomic skill sessions through observation, command, and clock interfaces. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/atomic_actions/runner.py#L335-L335) · [Atomic Skills execution guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/sim/atomic_actions/index.md) · [ExecutionRunner documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.atomic_actions.rst) | -| Interactive grasp annotation | Offers browser selection of a graspable mesh region. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/graspkit/scripts/annotate_grasp.py#L35-L35) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/grasp_generator.rst) | -| Mesh grasp backend | Coordinates sampling, annotation, collision checks, and caches for one mesh. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/graspkit/pg_grasp/_antipodal_backend.py#L56-L56) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/grasp_generator.rst) | -| Gripper collision checks | Checks gripper geometry against the target mesh. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/graspkit/pg_grasp/gripper_collision_checker.py#L85-L85) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/grasp_generator.rst) | -| Antipodal sampling | Samples opposing surface contacts using mesh ray casting. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/graspkit/pg_grasp/antipodal_sampler.py#L52-L52) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/grasp_generator.rst) | -| Parallel-jaw gripper model | Defines physical gripper dimensions independently of the sampling algorithm. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/graspkit/pose_generator.py#L49-L49) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/grasp_generator.rst) | -| Integration catalog | Describes one Task Program integration and validates compilation against its fingerprint. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/integrations/catalog.py#L450-L450) · [Integration catalog documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.rst) | -| Object backend views | Adapts rigid and articulated object state to backend-neutral Scene batches. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/objects/backends/__init__.py#L17-L17) · [Object backend views documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | -| Parallel execution | Coordinates parallel semantic branches through command and safety interfaces. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/runtime/parallel_executor.py#L629-L629) · [Parallel execution documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.task_program.runtime.rst) | -| Program loading & validation | Loads bounded JSON or YAML into the canonical Task Program configuration. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/language/loader.py#L17-L17) · [Program loading & validation documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.task_program.language.rst) | -| SceneRegistry | Binds semantic entity identifiers to scene metadata and runtime providers. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/semantics/scene.py#L1083-L1083) · [SceneRegistry documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/scene_registry.md) | -| SimulationTaskProgramFactory | Binds simulated robots, planners, and services to construct the atomic skill engine. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/integrations/simulation/environment.py#L407-L407) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/index.md) · [Simulation integration API](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.simulation.rst) | -| RobotSkillProfile | Declares robot resources, skill bindings, and effect-assurance capabilities. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/semantics/profiles.py#L1497-L1497) · [RobotSkillProfile documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/robot_profiles.md) | -| EnvironmentStepClock | Maintains execution time on the environment step grid. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/gym/envs/task_program/bridge.py#L325-L325) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/index.md) · [Gym Task Program API](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.task_program.rst) | -| Surface deformables | Specializes deformable objects for cloth particle sets. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/objects/deformable/surface.py#L28-L28) · [Surface deformables documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | -| TaskProgramCfg | Declarative task configuration describing intent and execution structure. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/task_program/language/schema.py#L725-L725) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/overview/task_program/index.md) · [TaskProgramCfg documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.task_program.language.rst) | -| Toolkit directory | Standalone grasp sampling, collision-mesh preparation, and URDF assembly utilities. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/__init__.py#L17-L20) · [Toolkits documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/index.rst) | -| URDF components | Processes component links, joints, naming, and mesh references. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/urdf_assembly/component.py#L85-L85) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/urdf_assembly.md) | -| URDF connections | Applies component connection rules and sensor attachment joints. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/urdf_assembly/connection.py#L30-L30) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/urdf_assembly.md) | -| URDF mesh resources | Copies mesh resources and rewrites references for the assembled robot. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/urdf_assembly/mesh.py#L28-L28) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/urdf_assembly.md) | -| URDF sensor attachments | Processes sensor descriptions and attachment metadata for assembly. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/urdf_assembly/sensor.py#L73-L73) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/urdf_assembly.md) | -| URDF file output | Formats and persists the assembled URDF document. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/toolkits/urdf_assembly/file_writer.py#L28-L28) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/toolkits/urdf_assembly.md) | -| Volume deformables | Specializes deformable objects for volumetric soft-body particle sets. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/objects/deformable/volume.py#L36-L36) · [Volume deformables documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | -| Workspace analyzer | Analyzes robot reachability and metrics for reusable workspace caches. | [Source](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/embodichain/lab/sim/motion/workspace/analyzer.py#L182-L182) · [Workspace analyzer documentation](https://github.com/DexForce/EmbodiChain/blob/abd754a3d86e82a6838e3062adda001f8b097007/docs/source/features/workspace_analyzer/workspace_analyzer.md) | +| Antipodal grasp generator | Generates candidate grasp poses through a reusable parallel-jaw interface. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/graspkit/pg_grasp/pose_generator.py#L177-L177) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/grasp_generator.rst) | +| Newton autograd bridge | Connects Warp tape derivatives to PyTorch action and functional-state gradients. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/diff/bridge.py#L81-L81) · [Newton autograd bridge documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.rst) | +| BatchEntity | Defines shared configuration and instance operations for batched simulation entities. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/common.py#L34-L34) · [BatchEntity documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | +| URDF collision processor | Replaces mesh collisions with convex pieces and optionally recomputes inertia. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/acd/urdf_modifider.py#L17-L17) · [Convex decomposition documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/convex_decomposition.md) | +| BufferedGymCommandSink | Validates and buffers control commands for incremental Gym demonstration consumption. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/task_program/bridge.py#L834-L834) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/index.md) · [BufferedGymCommandSink documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.task_program.rst) | +| CompiledTaskProgram | Immutable compilation result with materialized task segments and analysis views. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/compiler/program.py#L962-L962) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/index.md) · [CompiledTaskProgram documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.compiler.rst) | +| Effect evidence | Collects typed evidence from registered providers for semantic effects. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/semantics/evidence.py#L438-L438) · [Effect evidence documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.semantics.rst) | +| ExecutionRunner | Advances atomic skill sessions through observation, command, and clock interfaces. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/atomic_actions/runner.py#L335-L335) · [Atomic Skills execution guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/sim/atomic_actions/index.md) · [ExecutionRunner documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.atomic_actions.rst) | +| Interactive grasp annotation | Offers browser selection of a graspable mesh region. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/graspkit/scripts/annotate_grasp.py#L35-L35) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/grasp_generator.rst) | +| Mesh grasp backend | Coordinates sampling, annotation, collision checks, and caches for one mesh. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/graspkit/pg_grasp/_antipodal_backend.py#L56-L56) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/grasp_generator.rst) | +| Gripper collision checks | Checks gripper geometry against the target mesh. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/graspkit/pg_grasp/gripper_collision_checker.py#L85-L85) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/grasp_generator.rst) | +| Antipodal sampling | Samples opposing surface contacts using mesh ray casting. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/graspkit/pg_grasp/antipodal_sampler.py#L62-L62) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/grasp_generator.rst) | +| Parallel-jaw gripper model | Defines physical gripper dimensions independently of the sampling algorithm. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/graspkit/pose_generator.py#L49-L49) · [Grasp generation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/grasp_generator.rst) | +| Integration catalog | Describes one Task Program integration and validates compilation against its fingerprint. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/integrations/catalog.py#L450-L450) · [Integration catalog documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.rst) | +| Object backend views | Adapts rigid and articulated object state to backend-neutral Scene batches. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/objects/backends/__init__.py#L17-L17) · [Object backend views documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | +| Parallel execution | Coordinates parallel semantic branches through command and safety interfaces. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/runtime/parallel_executor.py#L629-L629) · [Parallel execution documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.runtime.rst) | +| Program loading & validation | Loads bounded JSON or YAML into the canonical Task Program configuration. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/language/loader.py#L17-L17) · [Program loading & validation documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.language.rst) | +| SceneRegistry | Binds semantic entity identifiers to scene metadata and runtime providers. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/semantics/scene.py#L1083-L1083) · [SceneRegistry documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/scene_registry.md) | +| SimulationTaskProgramFactory | Binds simulated robots, planners, and services to construct the atomic skill engine. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/integrations/simulation/environment.py#L498-L498) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/index.md) · [Simulation integration API](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.simulation.rst) | +| RobotSkillProfile | Declares robot resources, skill bindings, and effect-assurance capabilities. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/semantics/profiles.py#L1497-L1497) · [RobotSkillProfile documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/robot_profiles.md) | +| EnvironmentStepClock | Maintains execution time on the environment step grid. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/gym/envs/task_program/bridge.py#L331-L331) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/index.md) · [Gym Task Program API](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.gym.envs.task_program.rst) | +| Surface deformables | Specializes deformable objects for cloth particle sets. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/objects/deformable/surface.py#L28-L28) · [Surface deformables documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | +| TaskProgramCfg | Declarative task configuration describing intent and execution structure. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/task_program/language/schema.py#L725-L725) · [Task Program guide](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/overview/task_program/index.md) · [TaskProgramCfg documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.task_program.language.rst) | +| Toolkit directory | Standalone grasp sampling, collision-mesh preparation, and URDF assembly utilities. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/__init__.py#L17-L20) · [Toolkits documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/index.rst) | +| URDF components | Processes component links, joints, naming, and mesh references. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/urdf_assembly/component.py#L85-L85) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/urdf_assembly.md) | +| URDF connections | Applies component connection rules and sensor attachment joints. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/urdf_assembly/connection.py#L30-L30) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/urdf_assembly.md) | +| URDF mesh resources | Copies mesh resources and rewrites references for the assembled robot. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/urdf_assembly/mesh.py#L28-L28) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/urdf_assembly.md) | +| URDF sensor attachments | Processes sensor descriptions and attachment metadata for assembly. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/urdf_assembly/sensor.py#L73-L73) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/urdf_assembly.md) | +| URDF file output | Formats and persists the assembled URDF document. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/toolkits/urdf_assembly/file_writer.py#L28-L28) · [URDF assembly documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/toolkits/urdf_assembly.md) | +| Volume deformables | Specializes deformable objects for volumetric soft-body particle sets. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/objects/deformable/volume.py#L36-L36) · [Volume deformables documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/api_reference/embodichain/embodichain.lab.sim.objects.rst) | +| Workspace analyzer | Analyzes robot reachability and metrics for reusable workspace caches. | [Source](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/embodichain/lab/sim/motion/workspace/analyzer.py#L182-L182) · [Workspace analyzer documentation](https://github.com/DexForce/EmbodiChain/blob/61b2fbee9682ddeb7b0bc09f64c018509d18a01b/docs/source/features/workspace_analyzer/workspace_analyzer.md) | ## Coverage and interpretation -Snapshot: 113 nodes, 113 source-reviewed relationships, and 114 statically extracted relationships across all views. +Snapshot: 115 nodes, 115 source-reviewed relationships, and 115 statically extracted relationships across all views. Import declarations do not establish runtime calls or execution order. Missing edges do not imply architectural independence. From 560a1573dd5a564d2d6b713c8aa404552538ee72 Mon Sep 17 00:00:00 2001 From: yuecideng Date: Sat, 10 Oct 2026 15:05:09 +0000 Subject: [PATCH 3/4] test(docs): account for scene expansion overview neighbour --- docs/architecture/web/tests/explorer.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/architecture/web/tests/explorer.spec.ts b/docs/architecture/web/tests/explorer.spec.ts index e726b9caf..904a35abb 100644 --- a/docs/architecture/web/tests/explorer.spec.ts +++ b/docs/architecture/web/tests/explorer.spec.ts @@ -162,8 +162,8 @@ test('large neighbourhoods keep the selected module readable above the fold', as }; await expect.poll(titleIsInsideCanvas).toBe(true); await page.getByRole('button', { name: 'Direct neighbours only', exact: true }).click(); - // The expanded overview includes DatasetManager as a direct neighbour. - await expect(page.locator('.react-flow__node-module')).toHaveCount(13); + // Scene expansion adds another caller of EmbodiedEnv to the overview. + await expect(page.locator('.react-flow__node-module')).toHaveCount(14); await expect.poll(titleIsInsideCanvas).toBe(true); }); From 490274abdc496ab277f255b096fc76edac8ca6e0 Mon Sep 17 00:00:00 2001 From: yuecideng Date: Sun, 11 Oct 2026 15:41:23 +0000 Subject: [PATCH 4/4] fix(scene): require bridge completion and preserve recording rejection --- .../topics/scene-expansion/scene-expansion.md | 19 +- ...k_program.integrations.scene_expansion.rst | 44 +- embodichain/lab/gym/envs/embodied_env.py | 35 +- .../integrations/scene_expansion.py | 92 ++- .../envs/task_program/test_scene_expansion.py | 588 ++++++++++++++++++ 5 files changed, 741 insertions(+), 37 deletions(-) diff --git a/agent_context/topics/scene-expansion/scene-expansion.md b/agent_context/topics/scene-expansion/scene-expansion.md index c3142c2c8..e2802aff1 100644 --- a/agent_context/topics/scene-expansion/scene-expansion.md +++ b/agent_context/topics/scene-expansion/scene-expansion.md @@ -39,6 +39,12 @@ physical operations. The measured callback supplies task-specific state and event evidence. Gym remains the owner of reset, stepping and recording; no new generation session or persistence writer is introduced. +The attempt guard spans preparation, execution and measurement. It suppresses +automatic reset and blocks persistence during explicit callback resets, even +when those resets replace episode annotations. Rejected recording buffers must +be discarded by reset before retrying; beginning another demo or replacing its +metadata does not create a clean recording boundary. + ## Invariants - A changed pose is a proper 4x4 entity-to-local-arena Z-up transform, excluding @@ -50,11 +56,14 @@ generation session or persistence writer is introduced. containers are owned; duplicate entity IDs and invalid configuration fail explicitly rather than becoming ordinary geometric rejections. - Initial checks, program completion and measured acceptance remain separate. - Empty or unavailable required checks cannot accept data; projected effects - do not replace physical evidence. -- Failed/unverified scene metadata blocks reset-time dataset, camera and - trajectory saves, including explicit commit rows. Reset consumes that - metadata. Acceptance is not a receipt proving persistence. + Program success requires normal completion and the acceptance mask of the + selected canonical bridge, alongside successful demo execution. Generic Gym + success cannot replace that evidence. Empty or unavailable required checks + cannot accept data; projected effects do not replace physical evidence. +- Failed or unverified recordings block reset-time dataset, camera and + trajectory saves, including explicit commit rows. Rejection stays attached + to the buffers until reset discards them; a successful retry without reset + cannot make those buffers eligible. Acceptance is not a persistence receipt. Use [robot workspace](../robot-workspace/robot-workspace.md) for reachability cache and frame contracts, [motion planning](../motion-planning/motion-planning.md) diff --git a/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.scene_expansion.rst b/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.scene_expansion.rst index e5f3cbcaa..6f6180fbe 100644 --- a/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.scene_expansion.rst +++ b/docs/source/api_reference/embodichain/embodichain.lab.task_program.integrations.scene_expansion.rst @@ -32,23 +32,40 @@ For valid initial states, ``execute_scene_variant`` creates a fresh bridge using the canonical ``EmbodiedEnv.create_demo_segments(task_program=...)`` path and passes the resulting segments through ``execute_demo_episode``. Handwritten segment-planner overrides do not replace the selected Task Program. The normal -Gym executor owns ``env.step()`` and suspends automatic reset during execution. +Gym executor owns ``env.step()``. Automatic resets are suspended throughout +preparation, execution, and measurement. Explicit resets during either callback +discard recording buffers while the attempt remains in progress; the attempt's +persistence guard survives replacement of episode metadata by those resets. Measured acceptance and persistence ----------------------------------- ``SceneVariantEpisodeResult`` preserves initial checks, program execution, and -measured task checks independently. Acceptance requires all three to pass. -Empty checks, unavailable evidence, a failed program, or failed measured goals -cannot accept the scene. A completed program with projected effects still -needs physical evidence from the measured validator. +measured task checks independently. ``program_completed`` records normal +completion of the selected canonical bridge, and ``program_completion_mask`` +owns its single-row acceptance snapshot. The execution entry point leaves the +mask empty when the bridge did not complete. ``program_success`` requires that +bridge evidence and a successful demo execution. Generic Gym success from a +legacy task override or early termination does not establish program completion. +Acceptance requires valid initial checks, ``program_success``, and passing +measured task checks. Empty checks, unavailable evidence, a failed program, or +failed measured goals cannot accept the scene. A completed program with +projected effects still needs physical evidence from the measured validator. The attempt's proposal, actual initial-state identity, and validation evidence are published under ``scene_expansion`` in the existing episode metadata. Preparation provenance is owned and recursively immutable; ``to_metadata()`` -returns JSON-compatible values. Failed or interrupted attempts remain -ineligible for reset-time dataset, camera, and trajectory persistence, including -explicit commit-row requests and failed-episode recording. +returns JSON-compatible values, including the canonical bridge completion +evidence. While an attempt is active, reset cannot persist dataset, camera, or +trajectory buffers, even after a preparation or measurement reset replaces the +pending metadata. Failed or interrupted recordings remain ineligible for these +writes, including explicit commit-row requests and failed-episode recording. + +Rejection belongs to the recording buffers until reset actually discards them. +Replacing episode annotations or starting another demo does not clear it. A +later successful attempt without that reset can return ``accepted=True`` but +cannot make the earlier rejected recording eligible to save. Discard the +recording before retrying, using ``env.reset(options={"save_data": False})``. The integration performs one attempt, propagates configuration and host errors, and creates no retry scheduler or persistence session. The caller closes the @@ -101,11 +118,12 @@ not substitute a constant passing check for physical evidence: The environment must already have its Task Program integration configured. ``prepare_scene`` begins each candidate from a clean recording boundary using -reset with ``save_data=False`` when needed. It captures state and reseeds -observations only after settling and refreshing bindings. The measured callback -reads terminal state and any required intermediate-event evidence; it returns -``ValidationResult`` rather than an arbitrary truthy value. Applications retain -their own bounded retry policy and exceptional-attempt cleanup. +reset with ``save_data=False`` when needed, including after any rejected or +interrupted attempt. It captures state and reseeds observations only after +settling and refreshing bindings. The measured callback reads terminal state +and any required intermediate-event evidence; it returns ``ValidationResult`` +rather than an arbitrary truthy value. Applications retain their own bounded +retry policy and exceptional-attempt cleanup. Public interfaces ----------------- diff --git a/embodichain/lab/gym/envs/embodied_env.py b/embodichain/lab/gym/envs/embodied_env.py index 03f2c483c..34891ed45 100644 --- a/embodichain/lab/gym/envs/embodied_env.py +++ b/embodichain/lab/gym/envs/embodied_env.py @@ -374,6 +374,8 @@ def __init__( self.affordance_datas = {} self._task_program_adapter: TaskProgramEnvironmentAdapter | None = None self._active_task_program_bridge: TaskProgramDemoBridge | None = None + self._scene_expansion_attempt_active = False + self._scene_expansion_recording_rejected = False extensions = getattr(cfg, "extensions", {}) or {} @@ -1034,16 +1036,24 @@ def _initialize_episode( "commit_env_ids must be a subset of the rows being reset." ) - # Scene acceptance is independent of Task Program completion. A - # rejected or interrupted single-row expansion attempt must not become - # saveable through a later reset, even with failed-episode recording or - # explicit commit rows enabled. Reset consumes and clears this metadata. + # Scene eligibility follows the recording buffers, not replaceable demo + # annotations. Preparation and measurement resets cannot commit an + # active attempt; rejected buffers stay blocked until a full row reset. episode_metadata = getattr(self, "_demo_episode_metadata", None) - if self.num_envs == 1 and episode_metadata: - scene_expansion = episode_metadata[0].get("scene_expansion") - if ( - isinstance(scene_expansion, Mapping) - and scene_expansion.get("accepted") is not True + if self.num_envs == 1: + scene_attempt_active = getattr( + self, "_scene_expansion_attempt_active", False + ) + if episode_metadata: + scene_expansion = episode_metadata[0].get("scene_expansion") + if ( + not scene_attempt_active + and isinstance(scene_expansion, Mapping) + and scene_expansion.get("accepted") is not True + ): + self._scene_expansion_recording_rejected = True + if scene_attempt_active or getattr( + self, "_scene_expansion_recording_rejected", False ): save_data = False env_ids_to_commit = torch.empty( @@ -1178,6 +1188,13 @@ def _initialize_episode( if 0 <= env_id < len(executed_qpos_history): executed_qpos_history[env_id].clear() + if ( + self.num_envs == 1 + and env_ids_to_process.numel() == 1 + and int(env_ids_to_process[0].item()) == 0 + ): + self._scene_expansion_recording_rejected = False + def _clear_expert_rollout_rows(self, env_ids: torch.Tensor) -> None: """Invalidate selected expert-buffer rows without clearing large frames.""" if self.rollout_buffer is None or len(env_ids) == 0: diff --git a/embodichain/lab/task_program/integrations/scene_expansion.py b/embodichain/lab/task_program/integrations/scene_expansion.py index 3da0d87eb..edaba1b67 100644 --- a/embodichain/lab/task_program/integrations/scene_expansion.py +++ b/embodichain/lab/task_program/integrations/scene_expansion.py @@ -23,6 +23,8 @@ from types import MappingProxyType from typing import TYPE_CHECKING +import torch + from embodichain.lab.gym.envs._json import json_safe_copy from embodichain.lab.gym.envs.demo import ( DemoEpisodeResult, @@ -113,12 +115,18 @@ class SceneVariantEpisodeResult: scene. Program completion alone does not imply acceptance. measured_validation: Required measured task checks after execution, absent when initial-state validation prevented execution. + program_completed: Whether the selected canonical bridge completed all + segment lifecycles normally. Generic Gym success is insufficient. + program_completion_mask: Owned single-row bridge acceptance snapshot, + available only after normal program completion. """ variant: SceneVariant preparation: ScenePreparationResult execution: DemoEpisodeResult | None measured_validation: ValidationResult | None + program_completed: bool = False + program_completion_mask: tuple[bool, ...] = () def __post_init__(self) -> None: if not isinstance(self.variant, SceneVariant): @@ -133,6 +141,32 @@ def __post_init__(self) -> None: self.measured_validation, ValidationResult ): raise TypeError("measured_validation must be a ValidationResult or None") + if type(self.program_completed) is not bool: + raise TypeError("program_completed must be a bool") + try: + completion_mask = tuple(self.program_completion_mask) + except TypeError as exc: + raise TypeError("program_completion_mask must contain bool values") from exc + if any(type(value) is not bool for value in completion_mask): + raise TypeError("program_completion_mask must contain bool values") + if self.program_completed and len(completion_mask) != 1: + raise ValueError("program_completion_mask must contain one environment row") + object.__setattr__(self, "program_completion_mask", completion_mask) + + @property + def program_success(self) -> bool: + """Return whether the canonical program and demo execution succeeded. + + Returns: + Success only with normal bridge completion and its acceptance mask. + """ + return ( + self.program_completed + and self.program_completion_mask == (True,) + and self.execution is not None + and self.execution.completed + and self.execution.all_success + ) @property def accepted(self) -> bool: @@ -144,9 +178,7 @@ def accepted(self) -> bool: """ return ( self.preparation.validation.accepted - and self.execution is not None - and self.execution.completed - and self.execution.all_success + and self.program_success and self.measured_validation is not None and self.measured_validation.accepted ) @@ -165,11 +197,9 @@ def to_metadata(self) -> dict[str, object]: self.preparation.metadata, field_name="preparation.metadata" ), "initial_validation": _validation_metadata(self.preparation.validation), - "program_success": ( - self.execution is not None - and self.execution.completed - and self.execution.all_success - ), + "program_completed": self.program_completed, + "program_completion_mask": list(self.program_completion_mask), + "program_success": self.program_success, "measured_validation": _validation_metadata(self.measured_validation), "accepted": self.accepted, } @@ -216,6 +246,9 @@ def execute_scene_variant( the existing transaction boundary: reset to commit an accepted episode or reset with ``save_data=False`` to discard it. Failed or unverified scene acceptance also blocks a later ordinary reset from saving this recording. + Automatic resets and persistence remain suspended throughout preparation, + execution, and measurement. A rejected recording stays ineligible until + the host resets its buffers, including before a later accepted attempt. Args: env: Initialized single-row EmbodiedEnv or a wrapper around one. @@ -239,6 +272,7 @@ def execute_scene_variant( Raises: TypeError: For invalid contracts, callbacks, or program types. ValueError: For unsupported batching or invalid attempt identifiers. + RuntimeError: If another scene expansion attempt is already active. Exception: Host, planning, execution, and measurement errors propagate after the episode is marked ineligible for persistence. """ @@ -265,6 +299,12 @@ def execute_scene_variant( raise ValueError(f"{name} must be a nonnegative integer") if execution_cfg is not None and not isinstance(execution_cfg, DemoExecutionCfg): raise TypeError("execution_cfg must be a DemoExecutionCfg or None") + previous_attempt_active = bool( + getattr(target, "_scene_expansion_attempt_active", False) + ) + if previous_attempt_active: + raise RuntimeError("a scene expansion attempt is already active") + previous_no_auto_reset = bool(getattr(target, "_demo_no_auto_reset", False)) pending: dict[str, object] = { "schema_version": 1, @@ -278,13 +318,16 @@ def publish(metadata: Mapping[str, object]) -> None: metadata, field_name="scene_expansion" ) - publish(pending) + target._scene_expansion_attempt_active = True + target._demo_no_auto_reset = True try: + publish(pending) preparation = prepare_scene(variant) if not isinstance(preparation, ScenePreparationResult): raise TypeError("prepare_scene must return a ScenePreparationResult") if not preparation.validation.accepted: result = SceneVariantEpisodeResult(variant, preparation, None, None) + target._scene_expansion_recording_rejected = True publish(result.to_metadata()) return result @@ -294,6 +337,9 @@ def publish(metadata: Mapping[str, object]) -> None: segments = EmbodiedEnv.create_demo_segments( target, task_program=selected_program ) + bridge = target._active_task_program_bridge + if bridge is None: + raise RuntimeError("canonical Task Program bridge was not created") execution = execute_demo_episode( env, episode_index=episode_index, @@ -301,14 +347,40 @@ def publish(metadata: Mapping[str, object]) -> None: execution_cfg=execution_cfg, segments=() if segments is None else segments, ) + program_completed = bridge.program_completed + if type(program_completed) is not bool: + raise TypeError("Task Program bridge program_completed must be a bool") + program_completion_mask: tuple[bool, ...] = () + if program_completed: + completion_mask = bridge.completion_mask + if not isinstance(completion_mask, torch.Tensor): + raise TypeError("Task Program completion mask must be a tensor") + if completion_mask.dtype != torch.bool or completion_mask.shape != (1,): + raise ValueError( + "Task Program completion mask must be bool with one environment row" + ) + program_completion_mask = tuple(completion_mask.detach().cpu().tolist()) measured = measured_validator() if not isinstance(measured, ValidationResult): raise TypeError("measured_validator must return a ValidationResult") - result = SceneVariantEpisodeResult(variant, preparation, execution, measured) + result = SceneVariantEpisodeResult( + variant, + preparation, + execution, + measured, + program_completed=program_completed, + program_completion_mask=program_completion_mask, + ) + if not result.accepted: + target._scene_expansion_recording_rejected = True publish(result.to_metadata()) return result except BaseException as error: + target._scene_expansion_recording_rejected = True pending["status"] = "error" pending["error_type"] = type(error).__name__ publish(pending) raise + finally: + target._demo_no_auto_reset = previous_no_auto_reset + target._scene_expansion_attempt_active = previous_attempt_active diff --git a/tests/gym/envs/task_program/test_scene_expansion.py b/tests/gym/envs/task_program/test_scene_expansion.py index 974d56611..6c39ae7d8 100644 --- a/tests/gym/envs/task_program/test_scene_expansion.py +++ b/tests/gym/envs/task_program/test_scene_expansion.py @@ -20,20 +20,43 @@ from collections.abc import Iterator from contextlib import nullcontext +from dataclasses import replace from types import SimpleNamespace from typing import Any +from unittest.mock import Mock import pytest import torch from embodichain.lab.gym.envs.demo import ( + DemoEpisodeResult, DemoExecutionCfg, DemoSegment, execute_demo_episode, ) +from embodichain.lab.gym.envs.base_env import BaseEnv from embodichain.lab.gym.envs.embodied_env import EmbodiedEnv from embodichain.lab.gym.envs.managers.record import record_camera_data +from embodichain.lab.gym.envs.task_program.bridge import ( + BufferedGymCommandSink, + EnvironmentStepClock, + RuntimeCommandFrameEncoder, + TaskProgramDemoBridge, +) from embodichain.lab.gym.envs.types import ControllerAction +from embodichain.lab.gym.envs.wrapper.no_fail import NoFailWrapper +from embodichain.lab.sim.atomic_actions.bindings import JointPositionTarget +from embodichain.lab.sim.atomic_actions.runtime_commands import ( + EndpointCommand, + JointPositionPayload, + RuntimeCommandFrame, +) +from embodichain.lab.sim.atomic_actions.state import ( + PlanningContext, + RobotObservation, + SceneSnapshot, + TaskState, +) from embodichain.lab.sim.motion.expansion import ValidationCheck, ValidationResult from embodichain.lab.sim.scene_expansion import ScenePoseChange, SceneVariant from embodichain.lab.task_program import ( @@ -45,11 +68,17 @@ ) from embodichain.lab.task_program.integrations.scene_expansion import ( ScenePreparationResult, + SceneVariantEpisodeResult, execute_scene_variant, ) +from embodichain.lab.task_program.runtime.results import ( + SemanticExecutionResult, + SemanticExecutionStatus, +) _GOAL_X = 0.5 _START_X = 0.2 +_STEP_DT = 0.02 def _program() -> TaskProgramCfg: @@ -163,6 +192,8 @@ def __init__(self) -> None: self._demo_active_rollout_start_steps = self.rollout_steps.clone() self._demo_episode_metadata = [self._new_demo_episode_metadata(0)] self._active_task_program_bridge = None + self._scene_expansion_attempt_active = False + self._scene_expansion_recording_rejected = False self._demo_no_auto_reset = False self.episode_success_status = torch.zeros(1, dtype=torch.bool) self._task_success = torch.zeros(1, dtype=torch.bool) @@ -637,3 +668,560 @@ def test_preparation_metadata_owns_and_freezes_nested_provenance() -> None: exported["preparation"]["nested"]["ids"][0] = "mutated-export" assert prepared.metadata["nested"]["ids"] == ("snapshot-v1",) assert result.to_metadata()["preparation"]["nested"]["ids"] == ["snapshot-v1"] + + +class _RuntimePort: + """Single-row runtime port for the production bridge and safe-stop sink.""" + + def __init__(self, sink: BufferedGymCommandSink) -> None: + self.sink = sink + self.cancel_count = 0 + self.frame = RuntimeCommandFrame( + commands=( + EndpointCommand( + JointPositionTarget(control_part="arm", joint_ids=(0,)), + JointPositionPayload(positions=torch.tensor([[_GOAL_X]])), + ), + ), + active_mask=torch.ones(1, dtype=torch.bool), + env_ids=torch.tensor([0]), + hold_duration=torch.tensor([_STEP_DT]), + ) + self._set_status(SemanticExecutionStatus.IDLE) + + def _set_status(self, status: SemanticExecutionStatus) -> SemanticExecutionResult: + self.status = status + self.result = SemanticExecutionResult( + status=status, + workflow_id="scene-expansion-fixture/segment-0", + current_call_index=0 if status is SemanticExecutionStatus.RUNNING else None, + env_ids=torch.tensor([0]), + success_mask=torch.tensor([status is SemanticExecutionStatus.COMPLETED]), + failure_mask=torch.zeros(1, dtype=torch.bool), + cancelled_mask=torch.tensor([status is SemanticExecutionStatus.CANCELLED]), + eligible_mask=torch.tensor( + [status is not SemanticExecutionStatus.CANCELLED] + ), + task_state=TaskState.empty(1, "cpu"), + ) + return self.result + + def start(self, *calls: Any, **kwargs: Any) -> SemanticExecutionResult: + self.sink.send(self.frame, timeout=1.0) + return self._set_status(SemanticExecutionStatus.RUNNING) + + def step(self) -> SemanticExecutionResult: + self._hold() + return self._set_status(SemanticExecutionStatus.COMPLETED) + + def cancel(self, reason: str) -> SemanticExecutionResult: + self.cancel_count += 1 + self.sink.cancel(self.frame.targets, timeout=1.0) + self._hold() + return self._set_status(SemanticExecutionStatus.CANCELLED) + + def _hold(self) -> None: + qpos = torch.tensor([[_GOAL_X]]) # Measured after the command was consumed. + self.sink.hold( + self.frame.targets, + PlanningContext( + robot=RobotObservation(timestamp=0.0, qpos=qpos, qvel=qpos.clone()), + task=TaskState.empty(1, "cpu"), + scene=SceneSnapshot.empty(), + env_ids=torch.tensor([0]), + ), + timeout=1.0, + ) + + def adopt_verified_task_state( + self, task_state: TaskState + ) -> SemanticExecutionResult: + return self.result + + +class _RealBridgeEnv(_CandidateEnv): + """Use the production bridge while replacing physical and semantic ports.""" + + def __init__( + self, *, terminal_success: bool, terminal_action_kind: str = "runtime_command" + ) -> None: + super().__init__() + self.terminal_success = terminal_success + self.terminal_action_kind = terminal_action_kind + self.action_kinds: list[str | None] = [] + self.post_policy_port = SimpleNamespace( + validate_policy=Mock(), + actions=Mock(return_value=(torch.tensor([[_GOAL_X]]),)), + post_policy_result=Mock(return_value=True), + post_policy_metadata=Mock(return_value={}), + ) + self.validator_port = SimpleNamespace( + validate_validator=Mock(), + validate=Mock(return_value=True), + validator_metadata=Mock(return_value={}), + ) + + def create_task_program_bridge( + self, program: CompiledTaskProgram + ) -> TaskProgramDemoBridge: + clock = EnvironmentStepClock(_STEP_DT) + encoder = RuntimeCommandFrameEncoder( + SimpleNamespace(current_qpos=lambda env_ids: torch.zeros(1, 1)) + ) + sink = BufferedGymCommandSink(encoder, clock) + self.runtime = _RuntimePort(sink) + call = SimpleNamespace(call_index=0, call="pick") + segment = SimpleNamespace( + segment_id="segment-0", + segment_index=0, + name="move-cube", + source_path=("program",), + implicit=False, + calls=(call,), + post_policies=(SimpleNamespace(),), + validators=(SimpleNamespace(),), + ) + compiled = SimpleNamespace( + program_id="scene-expansion-fixture", + iter_segments=lambda: iter((segment,)), + sequential_execution_analysis=lambda index: SimpleNamespace( + calls=(call.call,), execution_prefix_length=1 + ), + ) + return TaskProgramDemoBridge( + compiled, + self.runtime, + sink, + clock, + post_policy_port=self.post_policy_port, + validator_port=self.validator_port, + ) + + def step(self, action: ControllerAction) -> tuple[Any, ...]: + self.action_kinds.append(action.metadata.get("bridge_action_kind")) + obs, reward, terminated, truncated, info = super().step(action) + if ( + self.terminal_success + and action.metadata.get("bridge_action_kind") == self.terminal_action_kind + ): + terminated = torch.ones(1, dtype=torch.bool) + info = {"success": torch.ones(1, dtype=torch.bool)} + return obs, reward, terminated, truncated, info + + +@pytest.mark.parametrize("wrapped", (False, True)) +@pytest.mark.parametrize( + "terminal_action_kind", ("runtime_command", "program_post_policy") +) +def test_early_gym_success_cannot_accept_an_unfinished_canonical_program( + wrapped: bool, terminal_action_kind: str +) -> None: + env = _RealBridgeEnv( + terminal_success=True, terminal_action_kind=terminal_action_kind + ) + result = execute_scene_variant( + NoFailWrapper(env) if wrapped else env, + _variant(), + prepare_scene=env.prepare, + measured_validator=env.measure, + ) + + assert result.execution.completed and result.execution.all_success + assert not env._active_task_program_bridge.program_completed + assert not result.accepted + assert result.to_metadata()["program_success"] is False + assert env.runtime.cancel_count == int(terminal_action_kind == "runtime_command") + assert "runtime_abort_safe_hold" in env.action_kinds + if terminal_action_kind == "runtime_command": + env.post_policy_port.actions.assert_not_called() + else: + env.post_policy_port.actions.assert_called_once() + env.validator_port.validate.assert_not_called() + env.persist_via_reset(commit_env_ids=[0]) + assert env.dataset_manager.saved == [] + assert env.camera_recorder.saved == 0 + assert env.trajectory_saves == 0 + + +def test_normal_canonical_completion_includes_post_policy_and_validation() -> None: + env = _RealBridgeEnv(terminal_success=False) + result = execute_scene_variant( + env, _variant(), prepare_scene=env.prepare, measured_validator=env.measure + ) + + assert result.accepted + assert result.program_completed + assert result.program_completion_mask == (True,) + env.post_policy_port.actions.assert_called_once() + env.validator_port.validate.assert_called_once() + assert "program_post_policy" in env.action_kinds + env.persist_via_reset() + assert len(env.dataset_manager.saved) == 1 + + +def test_no_fail_wrapper_cannot_override_a_completed_bridge_rejection( + monkeypatch: pytest.MonkeyPatch, +) -> None: + env = _CandidateEnv() + bridge = _Bridge(runtime_passes=True) + + def segments() -> Iterator[DemoSegment]: + yield DemoSegment( + actions=(ControllerAction(value=torch.tensor([[_GOAL_X]])),), + validator=lambda: True, + ) + bridge.program_completed = True + bridge.completion_mask.fill_(False) + + monkeypatch.setattr(bridge, "iter_segments", segments) + monkeypatch.setattr(env, "create_task_program_bridge", lambda program: bridge) + result = execute_scene_variant( + NoFailWrapper(env), + _variant(), + prepare_scene=env.prepare, + measured_validator=env.measure, + ) + + assert result.execution.completed and result.execution.all_success + assert not result.accepted + assert result.program_completed + assert result.program_completion_mask == (False,) + env.persist_via_reset(commit_env_ids=[0]) + assert env.dataset_manager.saved == [] + + +class _SettlingEnv(_CandidateEnv): + """Exercise production auto-reset dispatch with CPU simulation ports.""" + + def __init__(self) -> None: + super().__init__() + self._elapsed_steps = torch.zeros(1, dtype=torch.long) + self.max_episode_steps = 1 # One settling step reaches the ordinary limit. + self.cfg.ignore_terminations = False + self.sim.render_frame = lambda: nullcontext() + self.automatic_resets = 0 + + def _preprocess_action(self, action: Any) -> Any: + return action + + def _step_action(self, action: Any) -> Any: + return action + + def _advance_physics(self) -> None: + pass + + def _update_sim_state(self, **kwargs: Any) -> None: + pass + + def _update_physical_objective(self) -> None: + pass + + def get_obs(self, **kwargs: Any) -> None: + return None + + def get_info(self, **kwargs: Any) -> dict[str, Any]: + return {} + + def get_reward(self, **kwargs: Any) -> torch.Tensor: + return torch.zeros(1) + + def _extend_reward(self, rewards: torch.Tensor, **kwargs: Any) -> torch.Tensor: + return rewards + + def _postprocess_action(self, action: Any) -> Any: + return action + + def check_truncated(self, **kwargs: Any) -> torch.Tensor: + return torch.zeros(1, dtype=torch.bool) + + def _hook_after_sim_step(self, **kwargs: Any) -> None: + self._demo_steps += 1 + self.rollout_steps += 1 + self._traj_steps += 1 + + def reset( + self, seed: int | None = None, options: dict[str, Any] | None = None + ) -> tuple[None, dict[str, Any]]: + self.automatic_resets += 1 + self.persist_via_reset(**(options or {})) + self._elapsed_steps.zero_() + return None, {} + + step = BaseEnv.step + + +def test_preparation_settling_cannot_auto_reset_and_commit_unverified_data() -> None: + env = _SettlingEnv() + + def prepare(variant: SceneVariant) -> ScenePreparationResult: + prepared = env.prepare(variant) + env.step(ControllerAction(value=torch.zeros(1, 1))) + return ScenePreparationResult(prepared.initial_state_id, _checks("failed")) + + result = execute_scene_variant( + env, _variant(), prepare_scene=prepare, measured_validator=env.measure + ) + + assert not result.accepted + assert result.execution is None + assert env.automatic_resets == 0 + assert env.dataset_manager.saved == [] + assert env.camera_recorder.saved == 0 + assert env.trajectory_saves == 0 + assert not env._demo_no_auto_reset + assert not env._scene_expansion_attempt_active + + +@pytest.mark.parametrize("phase", ("prepare", "measure")) +def test_explicit_reset_inside_an_attempt_cannot_commit_pending_data( + phase: str, +) -> None: + env = _SettlingEnv() + env.max_episode_steps = 2 # The explicit reset owns this boundary. + + def prepare(variant: SceneVariant) -> ScenePreparationResult: + prepared = env.prepare(variant) + if phase == "prepare": + env.step(ControllerAction(value=torch.tensor([[_GOAL_X]]))) + env.reset(options={"commit_env_ids": [0]}) + return prepared + + def measure() -> ValidationResult: + if phase == "measure": + env.reset(options={"commit_env_ids": [0]}) + return _checks() + + result = execute_scene_variant( + env, _variant(), prepare_scene=prepare, measured_validator=measure + ) + + assert result.accepted + assert env.dataset_manager.saved == [] + assert env.camera_recorder.saved == 0 + assert env.trajectory_saves == 0 + + +@pytest.mark.parametrize("explicit_commit", (False, True)) +def test_rejected_buffers_remain_blocked_after_ordinary_demo_replaces_metadata( + explicit_commit: bool, +) -> None: + env = _CandidateEnv() + env.physics_moves = False + rejected = execute_scene_variant( + env, _variant(), prepare_scene=env.prepare, measured_validator=env.measure + ) + assert not rejected.accepted + + env.physics_moves = True + retry = execute_demo_episode(env) + assert retry.all_success + assert "scene_expansion" not in env.get_demo_episode_metadata(0) + env.persist_via_reset(**({"commit_env_ids": [0]} if explicit_commit else {})) + + assert env.dataset_manager.saved == [] + assert env.camera_recorder.saved == 0 + assert env.trajectory_saves == 0 + + +@pytest.mark.parametrize("reset_buffers", (False, True)) +def test_a_later_scene_can_save_only_after_rejected_buffers_are_reset( + reset_buffers: bool, +) -> None: + env = _CandidateEnv() + env.physics_moves = False + execute_scene_variant( + env, _variant(), prepare_scene=env.prepare, measured_validator=env.measure + ) + env.physics_moves = True + prepare = ( + env.prepare + if reset_buffers + else lambda variant: ScenePreparationResult("same-recording", _checks()) + ) + + accepted = execute_scene_variant( + env, + _variant(), + prepare_scene=prepare, + measured_validator=env.measure, + attempt_id=1, + ) + assert accepted.accepted + env.persist_via_reset(commit_env_ids=[0]) + + assert len(env.dataset_manager.saved) == int(reset_buffers) + assert env.camera_recorder.saved == int(reset_buffers) + assert env.trajectory_saves == int(reset_buffers) + + +def test_empty_reset_cannot_clear_rejected_buffer_eligibility() -> None: + env = _CandidateEnv() + env.physics_moves = False + execute_scene_variant( + env, _variant(), prepare_scene=env.prepare, measured_validator=env.measure + ) + env._initialize_episode(env_ids=[], save_data=False) + env.physics_moves = True + execute_demo_episode(env) + env.persist_via_reset(commit_env_ids=[0]) + + assert env.dataset_manager.saved == [] + assert env.camera_recorder.saved == 0 + assert env.trajectory_saves == 0 + + +def test_empty_reset_during_preparation_does_not_reject_a_fresh_recording() -> None: + env = _CandidateEnv() + + def prepare(variant: SceneVariant) -> ScenePreparationResult: + env._initialize_episode(env_ids=[], save_data=False) + env.object_x = variant.pose_changes[0].arena_pose[0][3] + return ScenePreparationResult("fresh-recording", _checks()) + + result = execute_scene_variant( + env, _variant(), prepare_scene=prepare, measured_validator=env.measure + ) + assert result.accepted + env.persist_via_reset() + + assert len(env.dataset_manager.saved) == 1 + assert env.camera_recorder.saved == 1 + assert env.trajectory_saves == 1 + + +def test_failed_discard_preserves_rejection_after_metadata_is_replaced( + monkeypatch: pytest.MonkeyPatch, +) -> None: + env = _CandidateEnv() + env.physics_moves = False + execute_scene_variant( + env, _variant(), prepare_scene=env.prepare, measured_validator=env.measure + ) + with monkeypatch.context() as patch: + patch.setattr( + env.camera_recorder, + "discard_and_clear", + Mock(side_effect=RuntimeError("camera discard failed")), + ) + with pytest.raises(RuntimeError, match="camera discard failed"): + env.persist_via_reset() + + env.physics_moves = True + execute_demo_episode(env) + env.persist_via_reset(commit_env_ids=[0]) + + assert env.dataset_manager.saved == [] + assert env.camera_recorder.saved == 0 + assert env.trajectory_saves == 0 + + +@pytest.mark.parametrize("previous_no_auto_reset", (False, True)) +@pytest.mark.parametrize("failure_phase", (None, "prepare", "execute", "measure")) +def test_attempt_flags_restore_after_success_and_each_error_boundary( + previous_no_auto_reset: bool, failure_phase: str | None +) -> None: + env = _CandidateEnv() + env._demo_no_auto_reset = previous_no_auto_reset + env.fail_step = failure_phase == "execute" + + def prepare(variant: SceneVariant) -> ScenePreparationResult: + assert env._scene_expansion_attempt_active and env._demo_no_auto_reset + if failure_phase == "prepare": + raise RuntimeError("preparation failed") + return env.prepare(variant) + + def measure() -> ValidationResult: + assert env._scene_expansion_attempt_active and env._demo_no_auto_reset + if failure_phase == "measure": + raise RuntimeError("measurement failed") + return env.measure() + + if failure_phase is None: + assert execute_scene_variant( + env, _variant(), prepare_scene=prepare, measured_validator=measure + ).accepted + else: + with pytest.raises(RuntimeError): + execute_scene_variant( + env, _variant(), prepare_scene=prepare, measured_validator=measure + ) + assert env._scene_expansion_recording_rejected + + assert env._demo_no_auto_reset is previous_no_auto_reset + assert not env._scene_expansion_attempt_active + + +def test_measurement_interrupt_restores_flags_and_keeps_buffers_ineligible() -> None: + env = _CandidateEnv() + + def interrupt() -> ValidationResult: + raise KeyboardInterrupt + + with pytest.raises(KeyboardInterrupt): + execute_scene_variant( + env, _variant(), prepare_scene=env.prepare, measured_validator=interrupt + ) + + assert not env._demo_no_auto_reset + assert not env._scene_expansion_attempt_active + execute_demo_episode(env) + env.persist_via_reset(commit_env_ids=[0]) + assert env.dataset_manager.saved == [] + assert env.camera_recorder.saved == 0 + assert env.trajectory_saves == 0 + + +def _successful_demo() -> DemoEpisodeResult: + return DemoEpisodeResult( + episode_index=0, + length=1, + completed=True, + success=(True,), + terminated=(False,), + truncated=(False,), + terminal_reason="success", + ) + + +def test_result_requires_owned_canonical_program_completion_proof() -> None: + result = SceneVariantEpisodeResult( + _variant(), + ScenePreparationResult("settled-v1", _checks()), + _successful_demo(), + _checks(), + ) + assert not result.accepted + assert not result.program_success + + mask = [True] + accepted = replace(result, program_completed=True, program_completion_mask=mask) + mask[0] = False + assert accepted.accepted + assert accepted.program_completion_mask == (True,) + assert not replace(accepted, program_completion_mask=(False,)).accepted + + +@pytest.mark.parametrize( + "proof,error", + ( + ({"program_completed": 1}, TypeError), + ({"program_completion_mask": (1,)}, TypeError), + ({"program_completion_mask": None}, TypeError), + ({"program_completed": True, "program_completion_mask": ()}, ValueError), + ( + {"program_completed": True, "program_completion_mask": (True, False)}, + ValueError, + ), + ), +) +def test_result_rejects_malformed_program_completion_proof( + proof: dict[str, Any], error: type[Exception] +) -> None: + with pytest.raises(error, match="program_completed|program_completion_mask"): + SceneVariantEpisodeResult( + _variant(), + ScenePreparationResult("settled-v1", _checks()), + _successful_demo(), + _checks(), + **proof, + )