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:
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:
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:
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.
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:
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:
- 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:
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/2catalog and digest-checked component index; - installed
PluginFactorywithfastsim-plugin-runtime/1descriptor; - managed
prepare/start/stop/closelifecycle; - exact locked Scenario identity and
scene/behaviorreads; - compile-derived dynamic robot/articulation bindings and scoped control grants;
- required
control,control.targets,event.publish,run.lifecycle, andscenario.readservices, plus catalog-optionalfluid.emitters,planning.capture,scene.command, andvisualizationdependencies that become mandatory for a selected FluidMotion renderer, solver, or attachment Motion as applicable (particle_emitterrequiresfluid.emitters, whilevisualization_overlayand profiled cuRobo arm alignment requirevisualization); - deterministic direct-motion state seeded from the locked Scenario pose and
initial joints, plus an authoritative control-session context for each
joint.position@1submission; - 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
JointMotionandPoseMotion, 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 beforemission.motion.completed; - a typed visual-only FluidMotion emission composed by
fill, with an explicit public renderer: either a strict publish/hold/clearvisualizationoverlay or a paused, static-source-capture-gatedfluid.emittersoperation, completed before optional retreat or Action completion; - bounded progress, capture, and terminal events for public completion evidence;
- exactly one terminal
run.lifecyclerequest: 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:
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:
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.