The distribution identity is fastsim-plugin-mission; the component identity is plugin://fastsim/mission. The wheel publishes exactly the two FastSim entry point groups required by Core: fastsim.components and fastsim.plugins.

Task-owned solver declarations live under the Mission plugin's private plugins.mission.config.solvers mapping. For example, the native navigation backend needs no routine tuning because reverse motion and in-place rotation are enabled by default:

yaml
plugins:
  mission:
    use: plugin://fastsim/mission
    config:
      solvers:
        navigation:
          backend: fastsim_navigation
          role: navigation
          config: {}

The Motion selects that declaration by key. The goal is an absolute pose in the captured world frame:

yaml
scenario:
  behavior:
    task:
      - name: navigate
        type: navigate
        robot: mobile_robot
        motions:
          navigate:
            kind: navigation
            solver_profile: navigation
            pose: {x: 4.85, y: 2.30, yaw: 0.0}

There is no navigation-world authoring block. Mission derives the robot's vertical collision interval, costmap dimensions, rasterization, footprint, time parameterization, and debug output from the captured world through fixed runtime policy; there is no floor, costmap, or footprint YAML.

For direct-Motion diagnostics or public-contract acceptance, the optional world-capture path can still be enabled explicitly:

yaml
plugins:
  mission:
    use: plugin://fastsim/mission
    config:
      world_capture:
        enabled: true
        geometry: true

Each direct Motion then uses FastSim's public PluginClient to capture live joint, entity, link, articulation, and frame state. With geometry: true, it also captures geometry transforms and batch-resolves digest-pinned resources as bounded bytes. The same one-shot capture is revalidated immediately before the direct Motion is submitted. Reset or world change therefore fails closed. After controller convergence, a second static capture verifies the final joints and becomes the next Motion's committed state. This opt-in setting does not select a solver and cannot disable the capture required by a profiled Motion.

Profiled native navigation

Before each profiled Navigation Motion, Mission requests a coherent collision-aware planning capture (C0). It selects actual collision geometry that can interact with the moving robot, removes the target robot and its attached payloads from the obstacle set, and derives a world-Z query interval from their complete captured collision assembly. A 0.1 mm lower contact epsilon omits a supporting floor, while a 5 cm upper allowance retains near-overhead clearance. Only obstacles with strict positive vertical overlap are projected and rasterized into a bounded 2D NavigationCostmap. The target robot's actual colliders, plus attached payload colliders, are transformed into the robot-local MultiPolygonFootprint. After projection, every polygon from this actual moving COLLISION set receives the exact Minkowski sum with a 5 mm axis-aligned square, forming a fixed 5 mm L∞ safety envelope. This is Mission runtime policy, not a configuration field.

Digest-pinned mesh resources are obtained through FastSim 0.1.0a20's public batch geometry resolve/read contract. The catalog supplies identity, provenance, schema, byte-size, and digest evidence; it does not supply a Mission-specific local_bounds field. Mission verifies the canonical resource bytes, parses the supported mesh schema, and privately computes the local bounds used for projection and conservative selection. Verified raw payloads are held in a bounded digest-keyed LRU. FastSim 0.1.0a20 still publishes a complete coherent catalog, transforms, references, and dynamic state on every capture. The separate scene.geometry@2 API supports ID-scoped transform reads and bounded deltas, but this navigation path does not subscribe to it today. Mission consumes captured facts by ID, while an unchanged rigid mesh payload is resolved/read only once per cached resolution_key. A cold selected obstacle computes metadata and rasterizes in the same decode; decoded Python meshes are never retained. The public planning catalog already describes effective physical geometry: provider-applied entity-root scale is present in its collider scale, local/compound offsets, and captured world pose. Mission therefore forwards those validated facts unchanged and never multiplies the locked Scenario scale a second time. Locked uniform scale facts remain an admission consistency check; no navigation scale or bounds field is added. These policies do not change the FastSim, UniRoboSim, solver, or YAML contracts.

Mission pushes those immutable values into the selected fastsim_navigation session and calls its Hybrid A* planner. The returned NavigationPath becomes a time-parameterized, multi-frame ControlTrack; it is not collapsed into one base-joint target. The fixed Mission reference-track limits of 5.0 m/s translation and 3.0 rad/s yaw, together with motion_duration_s, establish a minimum interval. Mission reads max_command_rate_hz from the public target descriptor and privately rounds the interval upward to an integer cadence multiple; no public cadence helper or new timing field is required.

After planning and debug generation, Mission obtains a fresh collision-aware handoff capture (C1) and immediately invokes its public standard strict revalidation. Success yields the validated C2 capture and a one-shot public PlanningSubmissionHandle. Mission re-anchors the world path to the actual C2 embedded-base state and preserves the raw yaw winding. It validates the complete re-anchored path against the original C0 costmap, then uses the immutable-payload cache and public C2 batch lease for a focused C0-to-C2 delta check over the actual submission path.

Current focused evidence lets Mission seal the exact ControlChunk to the C2 dependency/observation and submit it directly through the public scoped control session with the one-shot handle. The navigation path uses no private Easy interface, custom revalidation callback, or borrowed validation candidate. A strict CAPTURE_STALE or freshness-only authority rejection performs a bounded same-plan retry with a new C1/C2 pair. Only focused stale evidence rebuilds from a new C0, including the live map, footprint, Hybrid A* path, timeline, and debug evidence. Other resource, contract, solver, authority, validation, or control failures fail closed. Capture and resource leases are closed on every exit, and the Task-owned solver session is closed at the run terminal state. Planning, debug generation, bounded whole-plan retries, and controller settlement share one end-to-end motion_timeout_s.

FastSim 0.1.0a20 strict revalidation compares the complete planning publication; expected_motion_entities does not scope that comparison. A world with unrelated facts changing beyond tolerance on every handoff therefore fails closed after the bounded retries and submits no control. Selective geometry reads cannot replace the one-shot authority seal.

Every complete planning attempt atomically writes a distinct PNG before the fresh C1 request and immediately publishes mission.navigation.debug.attempt with final: false, so stale or rejected attempts retain addressable evidence. After a successful submission, mission.navigation.debug publishes final evidence for the same file with final: true. Both events report the absolute path, SHA-256, byte size, image dimensions, and map/path revisions. Black is lethal obstacle occupancy; cyan outlines are footprint samples along the path, not obstacle ghosts or a motion-history trail. Embedded PNG metadata includes the costmap origin, resolution, width, and height. The path is <tempdir>/fastsim-navigation-debug/<sha256(run_id)[:16]>/mission-navigation-ordinal-<ordinal:08d>-attempt-<attempt:04d>.png (/tmp/fastsim-navigation-debug/... on a typical Linux host). Debug completes before the C1 capture, outside the fresh C1/C2 handoff window, and remains a mandatory atomic evidence step with no configuration switch.

Profiled cuRobo arm alignment

The FastSim cuRobo adapter accepts JointMotion and PoseMotion values that name an arm-role solver profile. The solver declaration belongs at plugins.mission.config.solvers; the Scenario Motion contains only the solver_profile key that selects it. A profiled PoseMotion freezes its named candidate set at admission, resolves world, robot, object, object_link, or ee against the coherent planning capture, and calls cuRobo v2 plan_to_pose. The returned selected-goal index closes the Task pose binding.

Each profiled arm Motion takes one complete collision-aware capture and derives the world collision set, captured joints, and solver root from it. The root is the captured world pose of the solver model's physical root link—for the G2 split models, mobile_base_link; for whole-body models, base_link—not an assumed Scenario or entity-root pose. cuRobo receives the world in robot-root coordinates, so its internal base origin is (0, 0, 0). That origin is a frame normalization, not a command: base axes are excluded from the solver trajectory and never submitted.

The robot YAML and public CuroboConfig.locked_joints overlay declare the immutable set of joints that are fixed in cuRobo and therefore excluded from optimization. Their numeric entries are startup/fallback values, not the required simulator pose. The overlay wins over same-name file entries and may add an outside-cspace name only when the resolved URDF proves that it is an independent non-fixed/non-mimic joint reachable from the configured root. The resulting name set cannot be added to or removed from at runtime. For the G2 whole-body profiles, that set includes mobile x/y/yaw and the selected gripper leader, producing a native twelve-axis upper-body layout (five body joints plus the selected seven-arm chain). The acceptance layout is this backend-resolved post-overlay descriptor, not the length of a raw cspace declaration in the source YAML.

Every profiled arm planning attempt starts from one coherent full-articulation simulator capture (C1). Mission consumes every axis declared by the resolved cuRobo model: active axes become the planner start state, while each non-carrier locked-joint position is bound into cuRobo's fixed transforms. Mission then rebuilds the native robot model and solver caches before geometry materialization. This is an in-memory FastSim adapter operation and requires no change to third-party cuRobo. Independent simulator joints intentionally absent from the cuRobo model remain capture-only and are not silently promoted to locks. In the current G2 profiles, the eight wheel steering/rolling joints are such model-excluded axes; four fixed mobile_base_link collision-sphere proxies represent the wheels. Embedded carrier x/y/yaw are the other exception: their effective values remain exactly 0.0, and their captured position and velocity must both be exactly zero. Other captured locked-joint velocities must be finite but are diagnostic only because PhysX/TGS can report non-zero instantaneous velocity for a position-stable joint. Mission then publishes mission.curobo.locked_state.verified for the C1-bound effective lock map. The sealed C2 capture never refreshes that map or rebuilds the model; it only checks every locked joint against its C1 value within the fixed 5e-4 SI position envelope and projects the observed C1-to-C2 secant drift over one minimum target command interval into that same envelope. This proves the handoff envelope, not a trajectory-long physical hold contract. An already-moved embedded carrier and periodic carrier lift remain unsupported by this production path. This Mission arm path does not call the generic projection/restoration periodic-lift or turn code, so its unit tests are not production +/-pi or multi-turn evidence.

Capture does not pre-apply the configured radius. It closes the complete set of eligible COLLISION geometry exposed by FastSim's public composed-Stage collision capture after collision-filter and moving robot/payload exclusions; this path is not a legacy UsdHelper Stage scan. The cuRobo materializer alone applies the required world_filter_radius: 3.0: a collider is admitted when its conservative shape bound intersects the closed 3D ball of radius 3 m centered at the captured cuRobo solver root. It then omits a whole collider when that collider's verified highest point relative to the captured native robot-entity root is at or below +0.15 m. This fixed 15 cm support-floor cutoff is not configurable; a collider that crosses the plane is retained in full. Only this maximum-Z test uses the native root; obstacle poses, radius filtering, kinematics, and goals remain solver-root-relative. The materializer's admitted and excluded ID sets are disjoint and their union must exactly equal the captured expected set; there is no silent drop or second radius filter.

collision_exclusion_objects is an explicit list of locked Scenario aliases for intentional contact goals. Mission applies those exclusions only to a cuRobo pose request whose pose.object names one of the listed aliases; other motions using the same solver keep the complete collision world. The aliases are resolved to canonical entity and collision IDs at capture time, including the reserved environment alias, and an unresolved or empty selection fails closed. This planner-only policy does not disable PhysX execution contacts.

cuRobo v2 does not load analytic primitive buckets into this Scene path. Cuboids therefore remain native cuboids, while spheres, cylinders, and capsules are submitted as deterministic conservative outer triangle meshes rather than inscribed approximations. A native-mesh signature binds each collision_id to its shape content, scale, and source identity: changing any of those rebuilds cuRobo's name-keyed Warp mesh cache, while a pose-only update can reuse it. Mission synchronizes and attests that materialization, then uses a Mission-private, version-pinned checker-data adapter to freeze the same solver's active collision slots and Warp BVH into Mission-owned WorldMeshProjection values. No cuRobo snapshot type crosses the backend boundary, and CollisionWorld remains the central planning representation.

The same central CollisionWorld and RobotAttachmentState facts drive payload collision. For one attached payload, Mission preserves its complete captured collision geometry, excludes it from the environment obstacle set, and invokes cuRobo's public fit_spheres_to_mesh and attachment-manager APIs. attached_object_sphere_budget is a positive explicit fit budget and native slot allocation; it defaults to 16 when the robot file declares the attached_object extra link. CuRobo may return fewer active spheres than the budget. Mission validates the exact returned count, finite positive radii, link-local readback, public FK, and sphere-set digest. It records coverage, surface-gap, protrusion, and volume metrics as evidence but does not impose a full-coverage threshold. A fit that exceeds the available capacity is rejected rather than truncated.

Before every planning call, Mission fail-closes on a fixed alignment gate. It compares exact model/layout and joint-state digests, native FK link poses, all configured collision spheres, and every admitted mesh in world coordinates. The non-configurable limits are 1 µm/1 µrad for the root, 2 mm/0.5 degrees for links, 2 mm/1 µm for sphere center/radius, and 1 mm for obstacle vertices; obstacle IDs and source-asset digests must match exactly, and collision geometry must first close between the materializer and loaded checker as an oriented float32 surface within 1 µm per coordinate. Loaded pose translation uses an L-infinity bound computed independently for every obstacle as 32 * float32_ulp(max(1 m, max(abs(expected_position)))); orientation remains bounded at 1 µrad. The alignment event records every sample's p50/p95/p99/max translation errors, axis/L2/angle maxima, bound range, and the worst object's delta, position scale, float32 ULP, multiplier, and resulting bound. Vertex IDs, face order, same-winding duplicate faces, and CuRobo process=True vertex merging do not affect that identity; reversed winding remains distinct. World-space comparison then uses a symmetric 1 mm nearest-surface gate. The loaded world content digest is evidence; an unequal source-world digest and raw indexed-topology differences are diagnostics only. The G2 left and right acceptance profiles each expose 154 base-robot collision spheres and eight planned axes (seven arm joints plus the corresponding inner-gripper joint). Mission also includes the actual active attached-payload spheres in the alignment sample. Mission publishes every sample and its overlay through the public visualization API, plus a transition-only alarm: rejection is immediate and recovery requires three consecutive accepted samples.

FastSim's public composed-Stage collision capture and the world materialized for cuRobo both retain complete indexed triangle surfaces; solver input is not converted to line geometry. Final evidence comes from the actual loaded collision checker, not from reconstruction of the materializer input. The public overlay renders admitted collision bodies as resource-backed mesh instances and instances one shared unit-sphere resource for the actual robot and attached-payload collision spheres. It no longer draws mesh/sphere wireframes, root frames, or link-error segments. The closed world_filter_radius region retains an object whenever it overlaps the object's conservative bound; objects fully outside that bound enter neither the loaded checker, numeric validation, nor the entity overlay. Mission separately atomically publishes a complete binary PLY, object manifest, and filled PNG from the loaded collision checker. The observer streams every PLY vertex and face, recomputes oriented triangle-surface object and aggregate content digests, and uses that filled PNG for acceptance; raw PLY topology remains diagnostic.

An accepted same-state request still invokes the real cuRobo v2 planner; Mission verifies that every returned waypoint remains at the captured state and records success without submitting control. For a non-same-state cspace or pose plan, Mission strictly revalidates the planning capture, binds the exact multi-frame arm ControlChunk to the resulting one-shot PlanningSubmissionHandle, submits through the scoped control session, and requires controller convergence. Frozen profiles submit no physical base control. When the backend's effective model itself includes the complete manifest-declared (x, y, yaw) carrier as its ordered prefix, those axes are active without a separate Mission permission switch. Mission rebases the cuRobo request so the captured physical base is (0, 0, 0), restores returned SE(2) samples to physical joint coordinates, and submits the carrier and the remaining model axes in one atomic ControlTrack. Stale handoff evidence retries within the fixed bound; other authority, alignment, trajectory, or control failures fail closed. Overlapping mobile-manipulation and navigation groups are supported serially inside one Mission execution; a Run must not grant those aliased physical axes to another concurrent control producer.

The companion G2 acceptance matrix contains exactly four configurations: split_left.yml, split_right.yml, whole_body_left.yml, and whole_body_right.yml. The split profiles use native eight-axis groups; the whole-body profiles use the effective native twelve-axis upper-body groups described above. The whole-body solver profiles lock mobile x/y/yaw at zero and the selected inner-gripper leader at -0.7; the Mission resource group matches the resulting native descriptor exactly. All four acceptance profiles keep the carrier frozen and submit no physical base control. The G2 asset matrix additionally loads base_navigation.yml and mobile_manipulation_left.yml / mobile_manipulation_right.yml. They expose active carrier layouts of three axes and carrier-plus-arm layouts of ten axes, respectively. Mission accepts either layout only when the selected resource group exactly matches the backend's effective descriptor. Partial, reordered, locked, non-SI, or non-prefix carrier layouts fail closed. The current production path also rejects unauthenticated yaw branch crossings and complete turns; it does not claim generic periodic projection. The existing run.yaml remains the navigation demo and is intentionally unchanged. The checked-out third-party cuRobo dependency has been updated at the Warp 1.13 compatibility boundary: legacy wp.torch.device_from_torch calls now use the public wp.device_from_torch API, and CPU plus real-CUDA mesh-cache regressions pass. This removes that specific dependency blocker in the current checkout; it does not by itself claim that all fourteen G2 matrix cases pass end to end. Converting the Scene to OBBs, disabling mesh collision, or silently dropping meshes remains an unacceptable fallback because each would weaken the collision and alignment contract.

Canonical Scenario shape

task is the ordered Action list. There is no schema, actions, external Task document, or compatibility alias inside behavior.

yaml
scenario:
  behavior:
    task:
      - name: close
        type: grasp
        robot: arm
        motions:
          close:
            kind: joints
            arm: arm
            joints:
              joint1: 0.0
              joint2: 0.0

FastSim keeps behavior as a mapping and locks it into the ExecutionPlan. The Mission plugin rejects unknown behavior fields and empty Task lists.

Physical and cuRobo attachment changes are independent Actions. They execute at their authored positions in the Task; Mission does not force the two states to stay synchronized:

yaml
scenario:
  behavior:
    task:
      - name: physically-attach-dish
        type: physical_attachment
        robot: g2
        motions:
          change:
            kind: physical_attachment
            arm: left_gripper
            operation: attach
            object: dish
            link: ee
      - name: add-dish-to-curobo
        type: curobo_attachment
        robot: g2
        motions:
          change:
            kind: curobo_attachment
            solver_profile: left_curobo
            operation: attach
            object: dish

The object name is resolved only through the locked Scenario alias map. A physical link accepts either a declared semantic frame or a direct authored link name. Mission derives deterministic command and attachment IDs, calls the granted public scene.command service, then verifies the settled scene and geometry attachment records in a newer coherent capture; physical detach omits link. The cuRobo Motion has no arm or link: its solver configuration owns the native attachment link. Kinematic and Fluid Motions reject attachment.

A visual cup-fill is a separate fill Action. Its exact recipe is pose:pre_fill? -> pose:fill -> fluid:dispense -> pose:retreat?. The required fill Pose performs positioning; the required dispense FluidMotion then runs as its own ordered step before an authored retreat:

yaml
- name: fill-cup
  type: fill
  robot: mobile_manipulator
  motions:
    pre_fill: null
    fill:
      kind: pose
      arm: left_curobo
      solver_profile: left_curobo
      pose: {source: inline, frame: world, data: [...]}
    dispense:
      kind: fluid
      verification: visual_only
      renderer: visualization_overlay
      source:
        kind: entity
        entity: faucet
        position_m: [0.0, 0.0, 0.8]
        direction: [0.0, 0.0, -1.0]
        inherit_velocity: false
      fluid: water
      rate_ml_s: 40.0
      volume_ml: 24.0
      seed: 23
    retreat: null

For a physical particle_emitter, the outlet and all physical parameters belong to FastSim Core's locked Scene declaration. The Mission dispense FluidMotion carries only the exact fluid/emitter reference in addition to the common verification and renderer fields:

yaml
scenario:
  scene:
    fluids:
      water:
        use: fluid://water
        emitters:
          fill_cup:
            bind: {entity: faucet}
            position_m: [0.0, 0.0, 0.8]
            direction: [0.0, 0.0, -1.0]
            flow:
              rate_ml_s: 40.0
              speed_m_s: 0.35
              spread_radius_m: 0.006
              spray_half_angle_deg: 6.0
            limit: {volume_ml: 24.0}
            trigger: {mode: manual}
            enabled: false
            seed: 23
  behavior:
    task:
      - name: fill-cup
        type: fill
        robot: mobile_manipulator
        motions:
          pre_fill: null
          fill:
            kind: pose
            arm: left_curobo
            solver_profile: left_curobo
            pose: {source: inline, frame: world, data: [...]}
          dispense:
            kind: fluid
            verification: visual_only
            renderer: particle_emitter
            fluid: water
            emitter: fill_cup
          retreat: null

The FluidMotion effect fields are flat siblings of kind; there is no effect wrapper and special.liquid is not accepted. A FluidMotion has no arm, solver_profile, or attachment; the closed codec rejects all three because emission neither submits a kinematic target nor changes attachment state. In the fill recipe, the adapter derives its internal capture target from the required preceding fill Pose, whose arm remains a normal PoseMotion field. The old receiver field is not part of either renderer's closed schema and is rejected. renderer is required and has no implicit fallback. Select visualization_overlay for a visual-only water stream through FastSim's public visualization service, or particle_emitter for the public fluid.emitters path. The overlay renderer accepts volume_ml or duration_s (not particle_count); volume is converted to display time as volume_ml / rate_ml_s. Mission compiles and budget-checks that plan during prepare, rejecting a display duration greater than or equal to motion_timeout_s before any control or effect-service side effect. Its fluid: water value is a semantic label, not a Scenario fluid alias. Only an optional entity/link source is resolved from the locked Scenario. Because the overlay is a static point layer, entity/link sources must explicitly set inherit_velocity: false; the authored spray_half_angle_deg expands the stream laterally within a fixed bound.

The overlay publishes one bounded, digest-keyed point stream in the freshly captured static source frame, requires a complete no-loss publish receipt, holds it on a bounded clock, and clears that exact primitive with a strict clear receipt. It never requests fluid.emitters, creates particles, consumes a reservoir, or claims where the emitted stream lands or how much any container receives. Cancellation and failure drain any in-flight public call and attempt the same exact clear before propagating the original error.

For particle_emitter, Mission resolves fluid + emitter only from the locked scenario.scene.fluids declaration during prepare; inline source, flow, limit, trigger, and seed fields are rejected. The referenced emitter must be on an enabled fluid, disabled, manual, reservoir-free, millilitre-rate based, and have exactly one finite volume, duration, or particle limit. One Task may reference a configured emitter once, and Mission instances in the same Run hold an exclusive process-local claim. The stable Mission operation_id is separate from the physical Core emitter_id, and its digest pins both Plan/Scenario identities and the canonical declaration.

emitter: fill_cup is the declaration ID under scenario.scene.fluids.water.emitters; it is not a trigger time. The configured emitter stays trigger.mode: manual and enabled: false until Mission reaches the ordered dispense Motion, where Mission calls set_enabled(true). A time-bounded emission uses limit.duration_s in that Scene declaration; limit.volume_ml or a particle limit may be used instead, but exactly one finite limit is required.

Core creates the configured emitter at generation start. Mission therefore uses status, then a fresh static source identity/frame capture using the internal target derived from the required fill Pose, then set_enabled(true) and status polling; it never calls start for this path. First adoption requires a zero-counter, non-flowing PAUSED result. The fresh capture proves only that the configured outlet's entity/link source has one authored identity and a stable source frame; the Core emitter bind is that outlet source, never a receiver. Its lease remains open while Mission publishes mission.liquid.requested and synchronously activates the same physical emitter. A Core reset may be adopted only as a new zero-counter PAUSED generation; any other unowned or mismatched state is rejected without mutation.

Requested, started, settled, failed, and settlement-unknown events retain the Action identity/ordinal, FluidMotion identity/global ordinal, renderer/evidence mode, request digest, capture identity, generation/tick, and source identity; they carry no receiver or attachment identity. Every particle-emitter event explicitly sets claims_particle_state: true because its public emitter status and counters are particle-state evidence, while keeping claims_reservoir_state: false and claims_received_volume: false. Overlay started/settled events also retain the complete publish receipt, and settled events retain the clear receipt; overlay events set all three claim flags to false. For the particle renderer, cancellation and failure first reconcile the configured identity and call set_enabled(..., false). A verified PAUSED, non-flowing result preserves counters and allows the same generation to retry. stop is only a secondary containment action when the same verified emitter remains flowing after pause; unknown identity or generation evidence is never mutated. The failure event contains the physical emitter_id and the best trusted counters. Failure-event publication is best effort and never replaces the original cancellation or execution exception.

The current public capture and fluid-emitter services do not share an authority token that can be passed to set_enabled. Keeping the fresh capture lease open and making activation synchronous closes Mission's await-level TOCTOU window, but it cannot formally exclude an independent concurrent source-identity writer between capture validation and activation. Such cross-service atomicity needs a future Core authority-token extension. The process-local Mission claim also does not replace a Core claim/lease when non-Mission consumers may control the same emitter.

visual_only is deliberate: an overlay proves only accepted-and-cleared developer visualization, while emitter counters prove only bounded visual emission. Neither proves a landing point, container association, or received volume; those are outside the FluidMotion contract. pour remains a pose recipe and is not an alias for FluidMotion.

Implemented FastSim path

  • static fastsim-component/2 catalog and digest-checked component index;
  • installed PluginFactory with fastsim-plugin-runtime/1 descriptor;
  • managed prepare/start/stop/close lifecycle;
  • exact locked Scenario identity and scene/behavior reads;
  • compile-derived dynamic robot/articulation bindings and scoped control grants;
  • required control, control.targets, event.publish, run.lifecycle, and scenario.read services, plus catalog-optional fluid.emitters, planning.capture, scene.command, and visualization dependencies that become mandatory for a selected FluidMotion renderer, solver, or attachment Motion as applicable (particle_emitter requires fluid.emitters, while visualization_overlay and profiled cuRobo arm alignment require visualization);
  • deterministic direct-motion state seeded from the locked Scenario pose and initial joints, plus an authoritative control-session context for each joint.position@1 submission;
  • fail-closed controller convergence and postprocessing for every kinematic Motion, plus bounded renderer settlement for every FluidMotion, before explicit Motion/Action completion and terminal Mission finalization;
  • public planning capture with live joints, scene/link/articulation state, frames, geometry transforms, bounded resource reads, SHA-256 verification, revalidation, and planning-authorized control submission: opt-in for direct Motions and forced collision-aware for profiled navigation or arm solving;
  • live collision projection, native Hybrid A* planning, deterministic debug evidence, and multi-frame navigation control;
  • profiled cuRobo JointMotion and PoseMotion, with native v2 cspace/pose planning, fixed world-alignment gating, public overlays, strict planning revalidation, and sealed multi-frame arm control for non-stationary results;
  • standalone physical attach/detach Motions through public scene.command, followed by an authoritative collision-aware capture that verifies the complete attachment record before mission.motion.completed;
  • a typed visual-only FluidMotion emission composed by fill, with an explicit public renderer: either a strict publish/hold/clear visualization overlay or a paused, static-source-capture-gated fluid.emitters operation, completed before optional retreat or Action completion;
  • bounded progress, capture, and terminal events for public completion evidence;
  • exactly one terminal run.lifecycle request: success after every Action and resource cleanup completes, or failed/cancelled when execution cannot finish;
  • Host-owned background execution, bounded timeout, cancellation, and cleanup.

Direct joint and navigation Motions can reach control without a solver profile. Direct navigation resolves a world x/y/yaw goal onto the one manifest-declared holonomic embedded-base group and does not run obstacle-aware planning. Profiled navigation instead follows the collision-aware Hybrid A* path above. Only controller-converged segments advance Mission's commanded state. Unsupported Task content fails during prepare; it is never silently downgraded.

Remaining Core interface gaps

The current FastSim public plugin surface still does not provide:

  • plugin checkpoint/restore;
  • planner discovery and a locked planner grant;
  • a general Task/pose resource grant;
  • object bindings for dynamic plugins.

Mission reports the remaining cases as explicit admission failures. Inline profiled pose planning, sealed cuRobo arm submission, and attachment mutation do not depend on those gaps: Mission owns its declared UniSolver session, uses Core's standard planning submission handle, and executes physical attachment Motions only through the granted scene.command service. Native profiled navigation remains unchanged and does not depend on a Core planner grant.

Install and verify

Use Python 3.11 or 3.12 with FastSim >=0.1.0a27,<0.2 and Producer Kit >=0.2.8,<0.3:

bash
python -m pip install .

# Include fastsim-navigation >= 0.1.3 for native Hybrid A* planning.
python -m pip install '.[navigation]'

fastsim plugins list examples/scenario.yaml \
  --registry examples/robot-index.yaml --offline --json
fastsim plugins diagnose examples/scenario.yaml \
  --registry examples/robot-index.yaml --offline --json

For source validation:

bash
python -m pip install -e '.[dev]'
python -m pytest -q
python -m ruff check src tests
python -m mypy --config-file pyproject.toml src/fastsim_plugin_mission
python -m build

The metadata-only robot in examples/ makes discovery and compilation reproducible; it is not a backend-loadable physical asset.