Agent skill

Author Usd Physics

by LunCoSim in LunCoSim/lunco-sim

Author or diagnose LunCoSim USD physics: rigid bodies, colliders, joints, joint frames, drives, gravity, collision filtering, and scene teardown.

Apache-2.0Auto-check passed

Install Author Usd Physics

skills CLI
$ npx skills add LunCoSim/lunco-sim --skill author-usd-physics -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install LunCoSim/lunco-sim author-usd-physics --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/LunCoSim/lunco-sim.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/author-usd-physics .claude/skills/author-usd-physics && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
author-usd-physics
GitHub stars
107
Token cost
~9.7k tokens
SKILL.md length
5,116 words
Files
1
Skills in repo
40
Repo updated
First seen
Licence
Apache-2.0

At a glance

Author or diagnose LunCoSim USD physics: rigid bodies, colliders, joints, joint frames, drives, gravity, collision filtering, and scene teardown.

  • Works in 6 steps: A joint is TWO FRAMES, not an axis → A prismatic joint CARRIES MOMENT → Gravity is authored per scene → …
  • Exploding vehicles
  • SKILL.md covers Live joint detachment is one…, Collision geometry and derived…, 1. A joint is TWO FRAMES, not… and 1b. A rotating assembly is…, plus 9 more sections
  • Calls kind

What it does

Author Usd Physics is an agent skill from LunCoSim/lunco-sim. Author or diagnose LunCoSim USD physics: rigid bodies, colliders, joints, joint frames, drives, gravity, collision filtering, and scene teardown. Use for exploding vehicles, wrong hinge or slider motion, detached parts, bad contact, or scene-owned physics state. The critical contracts are two joint frames, explicit body attachment, standard UsdPhysics fields, and teardown for every scene-owned write. Use build-vehicle for mobility assembly.

Its SKILL.md is about 9.7k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

The repository describes itself as: Collaborative Multiphysics Cosimulator For Space Missions 🌎🚀🌚. The licence is Apache-2.0.

When your agent uses it

  • Exploding vehicles
  • Scene-owned physics state

Example prompts

  • “/author-usd-physics”

Workflow steps

6 steps, taken from the step headings in SKILL.md.

  1. A joint is TWO FRAMES, not an axis
  2. A prismatic joint CARRIES MOMENT
  3. Gravity is authored per scene
  4. A scene owns more than its entities
  5. Reading the failure modes
  6. A part is not a body

What it can do on your machine

Read from SKILL.md and the folder at commit 43f1301. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • kind

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Author Usd Physics loads about 9.7k tokens when it runs. Until then it costs about 116 tokens; SKILL.md has 5,116 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~116
When it runs · the whole SKILL.md, loaded when a task matches
~9.7k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from LunCoSim/lunco-sim at commit 43f1301, republished under its Apache-2.0 licence (© LunCoSim). 5,116 words, ~9,733 tokens.

Download SKILL.mdSave it as .claude/skills/author-usd-physics/SKILL.md (or your agent's skills folder).
name
author-usd-physics
description
Author or diagnose LunCoSim USD physics: rigid bodies, colliders, joints, joint frames, drives, gravity, collision filtering, and scene teardown. Use for exploding vehicles, wrong hinge or slider motion, detached parts, bad contact, or scene-owned physics state. The critical contracts are two joint frames, explicit body attachment, standard UsdPhysics fields, and teardown for every scene-owned write. Use build-vehicle for mobility assembly.

Authoring physics in USD

Physics is authored in USD and projected onto avian. USD is the source of truth; the ECS is the projection. Use maintained UsdPhysics schemas for topology, limits, and commandable drives. When USD has no material schema for a physical concept, use the narrow LunCo applied API that owns that concept. Do not add a LunCo schema when standard USD already owns the fact.

For a physical landing member, apply PhysicsDriveAPI:linear to a PhysicsPrismaticJoint. Author the drive's stiffness, damping, maxForce, and explicit force type; targetPosition and targetVelocity retain the standard USD defaults unless the asset needs to state them explicitly. The native Avian joint is the sole axial mechanism in the existing substep schedule. Do not duplicate standard fields under lunco:*. Missing or invalid fields fail projection; they are never replaced by a target, force cap, or solver-resolution workaround.

Native joint angle and displacement commands preserve the configured drive model and force/torque limit. They enable the motor and change its position target with zero target velocity; author drive gains and limits through the standard USD drive owner rather than through command-side tuning.

Omitted joint limits retain standard unbounded travel. The native port adapter maps lower negative infinity and upper positive infinity to absent bounds; malformed nonfinite limits remain invalid. Do not add artificial finite travel limits to make a valid unbounded joint accept a command.

Startup joint topology is prepared while the shared fixed clock is held. The BigSpace physics bridge seeds authored body poses in PreUpdate; USD joints are projected in JointPreparation during Update, after USD simulation projection and before the generic JointAdmission owner. This phase must not wait for a first physics step to resolve bodies or seat a joint.

Initial body admission validates raycast-wheel support footprints against the composed Avian collider geometry and its hit normal. It does not require a LocalGravity sample from a fixed cycle that has not been admitted yet. Keep authored-pose validation in lifecycle preparation; the first synchronized physics cycle begins only after the complete admission set is ready.

For asset-level checks, the authored assembly_audit tool can inspect explicit composed joint body relationships, cardinal axes, optional local frames, rigid-body/joint coverage, and mass/inertia/collider manifests. Use those reports to diagnose authored topology before changing solver settings. Pass the exact Editor document id first to each stage-reading helper; () explicitly selects the mounted live scene, not the focused preview. The tool does not infer a body from a name, turn a raycast wheel into a rigid body, or replace the standard USD physics owner. For a single explicit decision per maintained part, use assembly_audit::physicality_report(doc, manifest). Entries choose physical or visual-only; physical entries reuse the existing body/joint and collider/mass fields, while visual-only entries require a non-empty reason and must have no composed collision envelope. Unknown roles, duplicate paths, missing prims, and incomplete coverage are errors. This is Rhai policy and orchestration; it does not add a second collider reader or Rust asset rule.

Live joint detachment is one lifecycle

DetachJoint is the generic entity-detach command. It is deliberately not limited to entities carrying PhysicsJointLink: an ordinary non-joint target uses the normal entity-removal path. A target carrying native or pending joint state is classified as a joint and must use the physics lifecycle marker. The command records any authored endpoint invalidation and never directly removes a native joint. The lunco-usd-avian-joints::JointAttachPlugin consumes that marker at its Update boundary and performs one transaction in this order:

  1. retire the Avian JointGraph edge and its PhysicsIslands entry;
  2. remove the native or pending typed joint and JointComponentId;
  3. release the transient joint collision filter;
  4. despawn the now-disposable joint entity.

The retirement owner only runs when a PhysicsJointDetachRequested marker is present. Idle updates skip its exclusive graph transaction and world query. Joint admission readiness is also gated by one combined query over the existing PendingUsdJoint and PendingJointAdmission markers. The owner rechecks the scene projection and endpoint readiness before each pending batch is admitted, while settled updates with no pending joint skip that scan.

Scene teardown uses the same graph-retirement helper. This shared owner is what prevents a component-removal observer and a recursive despawn from unlinking one island edge twice. A command or Twin script must never implement a second joint detach path, disable JointDisabled as a substitute, or infer joint completion from the command acknowledgement; verify that the joint entity is gone and both endpoint bodies continue finite stepping. If a target has joint state but no PhysicsJointLink, the command warns and rejects it; the authored topology linter is the repair/diagnostic path. Persistent intent journals the typed USD active-state/remove operation separately, while Interactive remains a throwaway live transition.

When an Editor workflow creates a new moving part, use assembly_edit::rigid_body_plan for the explicit body schema and mass facts, then add the shape, collider, and local transform in the same proposal. Use assembly_edit::revolute_joint_plan for a rotating part: pass both body paths, both authored frame anchors and quaternions, the cardinal joint axis, ordered degree limits, and the collision policy. These helpers return typed operations for review; they do not infer bodies from hierarchy or drive a mechanism with Euler poses. Use assembly_edit::fixed_joint_plan for a rigid adapter: it requires the same explicit two body paths, two local anchors, two local quaternions, and collision policy, and returns a standard PhysicsFixedJoint plan without mutating the document.

For higher-level construction, use the dynamically reloadable assembly_builder Rhai library. Its frame_plan, cube_plan, cylinder_shape_plan, movable_cube_plan, component_bundle_facts/component_bundle_plan, existing_rigid_body_plan, and hinge_plan compose the same explicit standard USD facts for new or referenced parts; it does not promote a completed body or reuse an occupied standard joint identity, while its placement and cube edge-alignment plans reject ambiguous parent/frame assumptions before the proposal reaches the USD owner. Keep the generated plan in one reviewed change set. A component bundle may author PhysicsMassAPI on its root and collision APIs on its geometry, but it does not infer a rigid body or joint; use the explicit body/joint/mount planners when the part is movable. A successful Rhai plan is not permission to omit the frame, collider, mass, or generation contract.

For an existing generic component whose geometry, frames, and mass must change together, use component_editor::update_plan with an explicit bundle recipe. Its component_editor::update_context/selected_update_context helpers are read-only discovery; the returned plan still goes through the normal proposal/review/commit path. The planner preserves topology and material ownership and rejects role drift, so it is not a replacement for explicit body or joint authoring.

For physical placement, use assembly_builder::place_with_clearance_plan with explicit Cube envelopes, or assembly_builder::place_with_collision_clearance_plan with exact moving and blocker body paths for referenced, Cylinder, Mesh, or compound bodies. The general tool reads aggregate QueryUsdPrim { collision_bounds: true } results from the shared collision owner and requires a translation-only common parent chain. Both paths reject overlap, insufficient gap, duplicate blockers, and ambiguous frame/geometry data before proposal. Do not rely on visual separation or an unreviewed generated USD transform to establish clearance. For deterministic mating of non-Cube bodies, use the companion align_collision_centers_plan or align_collision_edges_plan with exact paths and an explicit axis/edge/gap; these plans preserve authored physics facts and still require review before commit.

For reusable referenced parts, use assembly_builder::find_compatible_socket and assembly_builder::mount_component. These helpers select authored socket relationships and derive the reflected fixed/revolute/prismatic joint; the existing AttachComponent owner remains responsible for plug-frame math, reference lowering, occupancy, and atomic journalling. Do not copy a reference and guess a transform when the component already advertises a mount plug.

For a part that is already mounted, use assembly_builder::mount_frame_realignment_plan with the exact host, socket, part, and recorded-joint paths. It is a dynamic Rhai plan for nested canonical rigid frame composition and emits the part transform plus both exact joint anchors as one reviewed operation set. It preserves topology and fails before proposal on missing/ambiguous relationships, wrong plug/body identity, unsupported frame stacks, malformed values, or non-unit scale.

When a model is a reusable referenced assembly rather than a socket attachment, use assembly_builder::referenced_instance_plan or assembly_builder::referenced_instance_targeted_plan when the source prim must be explicit. Then wait for its composed children before using assembly_builder::select_variants_plan. This keeps first-use asset loading separate from the variant recompose and makes missing parents, occupied identities, and invalid targets fail in Rhai before review. Parameter edits use assembly_builder::parameter_plan; the resulting typed SetAttribute ops go through the same proposal/journal boundary as every other authored edit.

Mission-level recipes may compose these generic helpers through a Twin-local tool library. Keep those recipes and their study values in the owning Twin, not in the core asset library, and keep their output to explicit generic USD operations. The core should provide only reusable component facts, topology, placement, and typed-op validation.

Collision geometry and derived NURBS proxies

QueryUsdPrim { collision_geometry: true } reads the composed collider through the same USD-to-Avian geometry reader used by projection. Use it when a requirement must compare the backend shape to its visual or design geometry; authored radius/height, geometry_bounds, and collision_bounds answer different questions. This query predicts the geometry Avian will receive from the current composed USD. It does not prove that a live ECS entity has completed projection or is currently contacting the world.

For UsdGeomNurbsPatch, use the read-only PlanNurbsCollisionProxy query and the shared nurbs.rhai proposal. Pass max_refinement_delta_m in canonical metres. This bounds the sampled change between two successive tessellation levels; it is a deterministic convergence criterion, not a certified maximum distance from the exact rational surface. Review the proposal that creates an invisible, source-linked UsdGeomMesh carrying standard mesh-collision APIs. Projection re-cooks the source and rejects stale or edited derived geometry. Do not author a separate collider approximation by hand or treat this threshold as a CAD accuracy guarantee.

The Avian mesh adapter advertises its implemented USD approximation modes via AvianMeshCollisionApproximations: none, convexHull, and convexDecomposition. none is valid only for static or kinematic bodies. Other standard USD tokens remain valid authored values, but this adapter rejects them until it can realize their stated semantics; it does not silently substitute another shape.

The local avatar is a runtime kinematic camera embodiment, not an authored rigid body. Its MoveAndSlide capsule reuses the standard UsdPhysics colliders projected by the Avian bridge in the active BigSpace frame. Do not add an avatar collider schema or a second collision representation; see the BigSpace physics boundary for the frame conversion and Twin-scoped traversal policy.

1. A joint is TWO FRAMES, not an axis

This is the single most expensive thing to get wrong, and it fails silently.

A UsdPhysicsJoint is defined by a frame on each body:

USDavianmeaning
physics:localPos0 / localPos1JointFrame::anchorwhere the joint attaches, in each body's local space
physics:localRot0 / localRot1JointFrame::basishow the joint frame is ORIENTED, in each body's local space
physics:axisslider_axis / hinge_axis / twist_axisa CARDINAL axis of the joint frame — X, Y or Z is the whole vocabulary

physics:axis can only name a cardinal axis. That is exactly why localRot exists: it is how a mechanism that is not axis-aligned — a landing leg raked 25° off vertical — says where its axis really points.

Both halves must cross into the engine. Every avian joint except the spherical constrains relative ORIENTATION through basis1/basis2. An identity basis therefore demands its body sit square to the other body. Carrying the rake in the axis alone aims the slider correctly and still wrenches the strut 25° out of true — the constraint is violated from the first step, the solver resolves it impulsively through the strut's lever arm, and the vehicle disassembles at kilometres per second.

The rule when a body rests at an angle: if body1 is rotated relative to body0 in the authored rest pose, that rotation lives in the FRAMES. The joint holds rot0 · localRot0 == rot1 · localRot1, so author whichever side is needed to make that identity true at rest.

usda
# LegPX is raked +25° about Z (its own xformOp:rotateXYZ).
def PhysicsPrismaticJoint "LegPX_Spring" (
    prepend apiSchemas = ["PhysicsDriveAPI:linear"]
)
{
    rel physics:body0 = </DescentLander>
    rel physics:body1 = </DescentLander/LegPX>
    uniform token physics:axis = "Y"          # cardinal, IN the joint frame
    quatf physics:localRot0 = (-0.216440, 0, 0, 0.976296)   # 205° about Z: +Y down the strut
    quatf physics:localRot1 = (0, 0, 0, 1)                  # the same 180° flip; the leg body
                                                            # already carries the 25°
}

Quaternions in USD are (w, x, y, z). Angles — physics:lowerLimit, upperLimit, coneAngle0Limit, coneAngle1Limit — are degrees.

The diagnostic

The loader measures every joint against its authored frames at build and reports a violation, seating the body only where the constraint determines it uniquely:

[usd-avian] joint /…/PadNX_Weld starts violated by 0.000 m / 0.436 rad — seating
`/…/PadNX` onto the authored joint frame. frame0: localPos0=… localRot0=…,
frame1: localPos1=… localRot1=…, body0 at …, body1 at …

0.436 rad is 25°. An angular violation on a raked mechanism is almost always a missing physics:localRot1: body0's frame was authored off-cardinal and body1's was left at identity, so the joint demands body1 sit square to body0.

Position is checked for every joint type. Orientation is checked and seated only where all three rotational DOF are locked (fixed, prismatic); a revolute or spherical joint leaves rotation free by design, so it is reported and left to the solver.

1b. A rotating assembly is joints plus a controller — never Euler animation

An antenna, solar panel, camera gimbal, arm, or other tracking head has two separate concerns:

  1. Mechanism: each moving frame applies PhysicsRigidBodyAPI and is joined to its immediate predecessor with a standard PhysicsRevoluteJoint.
  2. Setpoint law: a Modelica program computes radians and wires them to the joint's native inputs:angle port. The cosim joint backend owns the position motor only while that port is wired.

The assembly owns its own hinges. Compose the assembly's root directly onto the host body and keep its internal joints and controller connections relative to that root. The host supplies no duplicate attachment joint; it only chooses to compose the assembly. Keep the visible mechanism under a unique child path.

This is the direction of dependency: a higher-level rover, lander, tower, or ground station knows it installs an antenna; the antenna knows only its own frames, joints, ports, and Modelica controller. The lower-level assembly never names Rover, Lander, or any other concrete host.

Never drive a tracking mechanism by xformOp:rotateXYZ. An authored rest orientation is allowed where it belongs in the joint frames; continuous yaw or pitch is a joint state. Euler writes bypass contacts, limits, solver state, and the measurable angle output, then race the physics bridge.

Coordinate contract for direction-tracking mechanisms

Before authoring or changing a tracker, write down and validate the complete chain: world direction convention, mount-frame conversion, each joint's positive axis/order, and the physical boresight of the final geometry. Do not assume the vehicle's conventional -Z forward is the antenna or panel boresight: inspect the composed asset. Derive the Modelica vector-to-angle equations from that actual geometry, then verify live that the target vector, controller setpoint, joint angle, and visible boresight agree. A controller's self-error/"locked" output alone is not evidence of physical pointing; it can validate the same wrong convention it commanded.

PhysicsRevoluteJoint is a generic mechanism, not a wheel marker. Do not infer vehicle topology from an antenna, solar tracker, or robotic-arm hinge.

2. A prismatic joint CARRIES MOMENT

A PhysicsPrismaticJoint locks all three rotational DOF. It is a slider, not a pin. This has a consequence that is easy to miss and impossible to see in a screenshot:

Because it carries moment, a sprung leg has two ways to absorb a landing: slide along its axis, which is the one you designed, or bend its angular lock, which you did not. The second is always available, and the solver will take it whenever a stray contact makes it cheaper.

The failure: a second contact steals the load path

A suspension has exactly one intended load path — foot → spring → chassis. Give the leg any second way to reach the ground and that path wins, because it is rigid and the spring is not. A contact that only sometimes touches is worse than one that always does: it latches on the first frame it grazes and never lets go.

The diagnostic is a conjunction: displacement remains zero, the joint force is absent or near zero under load, and the joint's angular lock is non-zero. Measure the angular error together with the reaction force; angular error alone is not enough because the joint solver is elastic.

Ground clearance is a load-path property, not a styling one. A raked box strut's bottom corner hangs half_thickness * sin(rake) below its tip, so a footpad centred on that tip clears it by almost nothing. Millimetres of margin on a metres-long vehicle is zero margin: a fraction of a degree of tip puts the strut on the ground. Size the foot so it is the only thing that can touch, by a margin no small rotation can close — and beware that half-measures make it worse, because a deeper foot demands a larger leg rotation to reach the ground, which brings the strut down faster than the foot drops.

Diagnosing it

Measure the angular lock directly from the two body orientations. A prismatic holds rot0 · localRot0 == rot1 · localRot1; the angle between the two authored joint axes is its constraint error. Then isolate contacts and inspect the composed collider geometry. A raked box can contact the ground with a corner; prefer a measured primitive or a deliberately authored proxy when the foot must be the sole load path. Change solver and friction parameters only after the geometry and contact ownership are verified.

Where a joint's rest position sits

Anchors left unauthored are DERIVED from the transform hierarchy, which puts displacement at exactly 0 in the authored rest pose. A leg authored -0.8 .. 0.0 therefore rests on its upper limit and travels one way only — by design: 0 is the fully-extended pose the geometry is drawn in, and the ground can only compress it.

So a stroke pinned at 0.0000 is not evidence the limits are backwards. Check the load path first. Widening the limit to make a jammed leg move buries the actual defect under a range the mechanism never needed.

Anchors are also why you cannot freely move a body to fix clearance. The anchor is derived from the body's origin; move the origin and you move the joint's zero, silently preloading the spring by the axial component of the shift. Change the part's extent, or the mating part, not the sprung body's origin.

2b. Author the body as a FRAME, with real dimensions

Make the body a frame, not a mesh

A prim that is both the rigid body and the geometry cannot host children, because its shaping transform applies to them too. Give the body its own frame and put the geometry inside it:

usda
def Xform "LegPX" (prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsMassAPI"])
{
    # origin at the hull anchor, local -Y down the leg
    double3 xformOp:translate = (2.519, 1.388, 0)
    double3 xformOp:rotateXYZ = (0, 0, 25.0)
    uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"]

    def Cylinder "Strut" (prepend apiSchemas = ["PhysicsCollisionAPI"])
    {
        uniform token axis = "Y"
        double radius = 0.075
        double height = 7.05                       # spans local y 0 .. -7.05
        double3 xformOp:translate = (0, -3.525, 0)
        uniform token[] xformOpOrder = ["xformOp:translate"]
    }
    def Cylinder "PadPX" (prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsCollisionAPI"])
    {
        double3 xformOp:translate = (0, -7.2, 0)   # 0.15 below the strut's tip
        ...                                        # + a joint, below
    }
}

Now every part of the leg is placed by how far down the leg it sits, in ONE frame, next to its neighbours. "The foot is below the strut's tip" is something a reader can see and a linter can compute. Place them in world coordinates instead and you have written the same geometry twice, in two frames — and the copies drift.

Show full SKILL.md (2,080 more words)Show less
Author DIMENSIONS, not scale

UsdGeomCube has only a uniform size, so any real box has to be faked with a non-uniform xformOp:scale. That scale then belongs to the prim rather than to the shape, and everything downstream has to remember it: the frame is unusable for children, the collider is a scaled shape rather than a measured one, and the part's true dimensions appear nowhere in the file.

Cylinder, Capsule, Cone and Sphere carry radius / height — the dimensions themselves, in metres. Prefer them, and prefer a Mesh with authored extent to a scaled primitive. A strut is a cylinder; modelling it as a squashed cube buys nothing and costs the frame.

3. Gravity is authored per scene

UsdPhysicsScene — the standard prim, physics:gravityMagnitude (scene units per second squared) and physics:gravityDirection (a vector in the stage's frame). Both convert at the boundary like every other authored quantity.

usda
def PhysicsScene "PhysicsScene"
{
    vector3f physics:gravityDirection = (0, -1, 0)
    float physics:gravityMagnitude = 1.62
}
  • This is a lunar simulator. Scenes are 1.62 unless there is a stated reason otherwise. The vehicles' drivetrains, struts and propellant budgets are sized for it.
  • ONE per scene. Two prims that disagree are an authoring error and are reported as one; the last read wins, which depends on prim order.
  • USD's sentinels are honoured: a NEGATIVE magnitude means "earth gravity", a ZERO direction means "the stage's down axis".
  • An orbital scene authors NO PhysicsScene. Gravity there is per-body and position-dependent (Gravity::Surface + the celestial point-mass model). A flat vector would override that and pin every spacecraft to a fictitious "down". assets/scenes/celestial/artemis_2_review.usda is the worked example.

Traction is gravity-dependent, and test thresholds must be too. At 1.62 a rover is traction-limited well below its drivetrain's omega_max * r ceiling (measured 2.2–2.4 m/s against 4.8). A kinematic ceiling holds at any gravity — a driven wheel cannot out-run its no-slip speed — but any floor derived from Earth traction is simply wrong on the Moon.

4. A scene owns more than its entities

Anything a scene load writes belongs to that scene. Unloading despawns the entities; SceneTeardown unloads everything else. Register teardown for every scene-owned resource or override so a new scene starts from its own contract.

Add a reset system beside the code that writes the state:

rust
app.add_systems(
    lunco_core::SceneTeardown,
    |mut commands: Commands| commands.remove_resource::<MySceneCache>(),
);

Which disposition is right depends on who owns the value:

  • REMOVE state that only means something while a scene is loaded — caches, provenance records. Absence is its correct empty state.
  • RESTORE state the app installs at start-up and a scene merely overrides. Gravity is the type case: a scene SHOULD override it, and must not leave the override behind. Removing it would leave the world with no value at all.

SceneTeardown grep-lists everything a reload restores. If you add scene-derived state and do not register it, you have added a leak.

The shared physics owner clears PhysicsHolds and PhysicsStepRequest and restores a zero-delta Time<Physics> at this boundary. The time owner keeps SimTick and Time<Fixed> continuous across scene changes; it reanchors the new mission epoch at the current tick and holds fixed cycles until the new scene is ready. Any admitted fixed-time balance remains with that shared clock and advances only after its causal holds clear. The controller/core owners clear scene-keyed input and control-path state. Do not add an asset-local pause flag; readiness and deliberate physics stepping must be owned by the incoming scene.

5. Reading the failure modes

SymptomLook at
origin.is_finite() panic in obvhsa body reached ±inf; a raycast was issued from it. The cause is upstream — find the first body has non-finite state diagnostic
[physics] body left the world: … or body has non-finite state: …first escapee names the mechanism that diverged. The required physics.body_escape Rhai policy pauses that dynamic joint island by default, including its colliders, while other bodies continue; inspect the first escapee to find the upstream cause. Bodies at the end of a lever arm (pads, wheels) escape first
joint … starts violated by … rada joint frame; see §1
stroke reads exactly 0.0000 in every regimea second contact carrying the load (§2). Measure the joint's angular-lock error before touching its limits
a spring loads the "wrong way"almost never the joint. A jammed DOF and a reversed one look identical from the port; §2 tells them apart
a scene retains another scene's settinga resource or override missing from SceneTeardown (§4)
a part is lying on the ground behind the vehicleit declared its own body and no joint holds it (§6). --validate the asset, or cmd("RunLint", #{}) the scene

6. A part is not a body

PhysicsRigidBodyAPI declares a body, and the loader honours it wherever it appears — ancestry is never consulted, because nesting-plus-joint is exactly how a wheel is mounted (Wheel_FL under the chassis + a PhysicsRevoluteJoint). So a prim that applies it and is jointed to nothing is a free body inside your vehicle, and it leaves:

usda
def Xform "Rover" (prepend apiSchemas = ["PhysicsRigidBodyAPI"]) {
    def Xform "Motor_FL" (prepend apiSchemas = ["PhysicsRigidBodyAPI"]) { … }   # ❌ falls out
    def Xform "Motor_FL" (prepend apiSchemas = ["PhysicsMassAPI"])      { … }   # ✅ part of the rover
}

The rule. Hierarchy is namespace; a joint is attachment.

Ownership follows from that, and it stops at every body boundary in BOTH directions — USD and avian agree:

what you authorwhat it becomes
collider with no body ancestorstandalone STATIC geometry
collider under a bodya piece of that body's compound shape
collider under a nested bodythat nested body's piece — never the parent's
a nested bodya SEPARATE body; a joint attaches it, or it falls off

The body prim may also carry PhysicsCollisionAPI. If it has a collision shape and descendants, both the body-root shape and every eligible descendant shape belong to the same compound. The root shape is identity-local to the body; its authored transform is applied by the body's ECS transform once, while child transforms are accumulated from the body frame. This remains true when the body arrives through a reference or a prepared runtime-instance projection.

physics:collisionEnabled=true is an opt-in on an already-declared collider; it does not apply PhysicsCollisionAPI by itself. Ordinary vehicle geometry must carry both. LunCoTerrainAPI is the owner-level projector exception. Raycast PhysxVehicleWheelAPI wheels are query-only and must not carry PhysicsRigidBodyAPI or PhysicsCollisionAPI; physical wheels are the other standard realization and must author both APIs on their collision geometry. The collision-enabled-without-api, raycast-wheel-collision-contract, and physical-wheel-collision-contract rules are errors, not runtime fallbacks.

Terrain collider ownership is mode-specific: lunco:assetMode="mesh" waits for the USD-to-Avian mesh bridge, while "dem" and "layered" use the native SurfaceOracle collider from lunco-terrain-surface. Do not author or project a second mesh collider over DEM-backed terrain.

Initial poses are admitted against the live terrain after the USD pose bridge has seeded every body. Convex colliders use their actual support geometry; composite and non-convex shapes retain the conservative AABB check. A persisted pose that overlaps after a terrain rebake is held as an authored error. Repair it with the typed TransformEntity command or remove it with DeleteEntity; do not clear the pending marker or silently translate the body in Rust.

Every renderable gprim under a composed kind = "assembly" vehicle must also state who owns its collision contract. A supported enabled PhysicsCollisionAPI shape is the ordinary owner. A raycast PhysxVehicleWheelAPI prim owns a support query, not a collision shape; physical wheel realization is selected by a standard revolute joint targeting the wheel and requires the authored body/collision pair. Intentional decoration must explicitly author physics:collisionEnabled = false or an inherited purpose = "guide". When a body has a purpose = "proxy" shape, its purpose = "render" geometry is the visual description and is excluded from collision. Leaving visible vehicle geometry unannotated is an error reported by vehicle-part-collision-contract, not an invitation for a runtime fallback.

Hierarchy is namespace. The joint is what attaches — nesting a body without one is the motor bug (nested-body-no-joint), and nesting one with a joint is how a foot mounts on a leg and a wheel on a chassis. Both directions of that rule matter: fold a nested body's collider into its parent's compound and one shape has two owners, the compound holding it rigidly while its joint pulls it. They fight every step until a body leaves the world.

  • An internal part (motor, gearbox, battery, panel, lamp) = mass + geometry, no body. Its colliders fold into the host body's compound, its mass belongs to the host.
  • A part that must move relative to its host = a body and a joint, authored together. That is what a mount (AttachSpec) writes, and it is why mounting/demo_probe.usda may keep its body.
  • Same answer in every robotics dialect: URDF lumps a fixed-jointed link into its parent's inertia, MJCF welds a jointless nested body, and neither has a notion of a link inside a link attached to nothing. The complete rotational assembly inertia belongs on the moving body's authored inertia contract; a domain component that produces torque must not create a second hidden body or duplicate shaft state.
Collision filtering

JointCollisionDisabled covers only the bodies named by a joint. For additional contacts, author PhysicsFilteredPairsAPI explicitly; for many-to-many sets, use PhysicsCollisionGroup with UsdCollectionAPI. Do not infer a vehicle-wide self-exclusion: articulated mechanisms often need selected internal contacts.

Filtering is symmetric, targets may name a body or a collider under that body, and invalid targets or same-compound pairs must remain visible diagnostics. The filter is installed during PhysicsSystems::Prepare, before contact generation. Verify contact itself in a scene test such as scenes/tests/filtered_pairs.usda or collision_groups.usda, not only by checking subsequent motion.

6b. purpose — which geometry is the collision geometry

UsdGeomImageable.purpose is inherited and separates display geometry from collision geometry:

purposedrawncollided
default (nothing authored)yesyes
renderyesonly when the body has no proxy
proxynoyes — this is the collision shape
guidenonever

Use proxy for an authored collision shape alongside render geometry. A guide prim is never a body or collider. purpose does not replace the frame contract in §2b; proxy geometry still inherits its authored frame.

Validate and run the scene
bash
"$LUNCOSIM_BIN" --validate assets/vessels/rovers/skid_rover.usda
[usd/nested-body-no-joint] /SkidRover/Motor_FL — applies PhysicsRigidBodyAPI
inside the body </SkidRover> but no joint names it — …

and on the loaded scene, run the same rules through the verb:

rhai
cmd("RunLint", #{}); query("GetDiagnostics", #{scope: "loaded_stages"});

The rules are authored in assets/scripting/policy/lint_usd.rhai — add one there rather than in Rust. Pair the lint with a scene test such as scenes/tests/parts_attached.usda, because lint cannot simulate motion. See validate-assets and docs/architecture/lint-substrate.md.

6b.1 Vehicle-level stability acceptance

Mounted-part linting catches free bodies, but it cannot prove a rover will stay upright under drive. Add a real flat-ground or production-terrain scene test that settles the assembled vehicle, drives through its authored control path, and reports:

  • travel and enough fixed-step samples;
  • maximum body tilt;
  • missing or detached descendants and their worst relative displacement; and
  • any domain output that is part of the vehicle acceptance, such as solar power and battery state.

On lunar regolith, inspect the force application height and the authored physics:centerOfMass before changing solver parameters. If all driven wheels reach the friction limit and the centre of mass is high above the contact plane, the launch pitch moment is physical. Lower or correctly assemble the mass distribution and rerun the test; do not turn a repeatable tip into a larger tilt allowance or a rendering filter.

For reusable authored evidence, use physics_acceptance::sample, contact_report, and the corresponding contact_acceptance, joint_distance_acceptance, motion_acceptance, and settling_acceptance helpers. Supply limits from the fixture or Twin model; the library only reads the production telemetry and diagnostic surfaces. Use system_evidence/system_acceptance to keep readiness and binding failures visible beside physical measurements. Do not add a vehicle-specific acceptance helper or change the solver to satisfy a threshold.

6c. What this engine does NOT read

Before authoring a schema because a DCC offers it, check that the importer consumes it. The full table is in docs/architecture/21-domain-usd.md. Commonly mistaken fields include:

  • PhysicsArticulationRootAPI — avian has no reduced-coordinate articulation; do not expect it to change this runtime.
  • UsdGeomPointInstancer — the composed visual reader supports static direct renderable Gprim prototypes and standard array/mask semantics. Time-sampled arrays, animated prototypes, and arbitrary prototype subtrees fail visibly because their runtime sampling or visual multi-mesh batching is not yet implemented. Native instanceable = true scenegraph instancing remains outside the runtime projection.
  • proxyPrim — not read; purpose on a sibling covers the case we have (see §6b).

An unconsumed field has no runtime effect. Use a supported schema or implement the owner and its projection before authoring the field.

Verify it, headlessly

luncosim test runs one authored scene plus its scenario deterministically, and its exit code comes from a telemetry verdict:

"$LUNCOSIM_BIN" test \
    --scene scenes/tests/landing_legs.usda --max-ticks 500

A physics change is not done until a scene runs clean: zero left the world, zero starts violated, and the scenario's own verdict PASSing. See author-scenario for writing the verdict.

For raycast rover wheels, check the Cylinder axle and lunco:wheel:headingAxis together: travel is steering-axis cross axle-axis, transformed by the physical mount. Collinear axes are invalid. Align front/rear station names and the Modelica drivetrain forward_yaw_offset with that travel direction; native navigation uses −Z at zero offset, +X uses −π/2. Verify departure physically.

© LunCoSim, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills/author-usd-physics of LunCoSim/lunco-sim.

Open the folder on GitHubat commit 43f1301

Compare with similar skills

Author Usd Physics next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Author Usd Physics compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Author Usd Physics this skillLunCoSim/lunco-sim107—~9.7kAutomated safety check: PassApache-2.0
Hermes Agent Skill AuthoringNousResearch/hermes-agent252k—~3.6kAutomated safety check: PassMIT
Diagnose Gatewayopenclaw/openclaw392k—~670Automated safety check: PassMIT
Configuring Oauth2 Authorization Flowmukul975/Anthropic-Cybersecurity-Skills34k—~1.7kAutomated safety check: PassApache-2.0
Authoring Skillsvercel/next.js143k—~1kAutomated safety check: PassMIT
Detecting Broken Object Property Level Authorizationmukul975/Anthropic-Cybersecurity-Skills34k—~4kAutomated safety check: PassApache-2.0

Similar skills

  • Hermes Agent Skill Authoring

    NousResearch/hermes-agent

    Author in-repo SKILL.md files: frontmatter and structure. An agent skill from NousResearch/hermes-agent.

    252k GitHub stars~3.6k tokensUpdated today
    Agent WorkflowsAuto-check passed
  • Diagnose Gateway

    openclaw/openclaw

    Diagnose Gateway, config, secrets, channels, and port failures with read-only one-liners.

    392k GitHub stars~670 tokensUpdated today
    Auto-check passed
  • Configuring Oauth2 Authorization Flow

    mukul975/Anthropic-Cybersecurity-Skills

    Configures secure OAuth 2.0 authorization flows, including Authorization Code with PKCE, Client Credentials, and Device Authorization Grant, covering flow selection, PKCE implementation, token…

    34k GitHub stars~1.7k tokensUpdated 1 mo ago
    Backend & APIsAuto-check passed
  • Authoring Skills

    vercel/next.js

    Official

    How to create and maintain agent skills in .agents/skills/. An agent skill from vercel/next.js.

    143k GitHub stars~1k tokensUpdated today
    Agent WorkflowsAuto-check passed
  • Detecting Broken Object Property Level Authorization

    mukul975/Anthropic-Cybersecurity-Skills

    Detect and test for OWASP API3:2023 Broken Object Property Level Authorization (BOPLA), covering excessive data exposure in API responses and mass assignment via injected request-body properties.

    34k GitHub stars~4k tokensUpdated 1 mo ago
    SecurityAuto-check passed
  • Diagnose

    github/awesome-copilot

    Official

    Perform a systematic diagnostic scan of an AI workflow across 5 quality dimensions — prompt quality, context efficiency, tool health, architecture fitness, and safety — producing a scored report…

    40k GitHub starsUsed in 1 repo~1k tokens
    Agent WorkflowsAuto-check passed

More from LunCoSim/lunco-sim

All 40 skills in this repo
  • Nightly Changelog

    LunCoSim/lunco-sim

    Generate concise LunCoSim nightly GitHub release notes with platform downloads, installation guidance, an AI-agent mission prompt, and a changelog link.

    107 GitHub stars~1.1k tokensUpdated today
    Auto-check passed
  • Build or repair a reusable scene component through a live LunCoSim Editor session.

    107 GitHub stars~4.4k tokensUpdated today
    Auto-check passed
  • Assembly Quality

    LunCoSim/lunco-sim

    Build or review a componentized LunCoSim USD assembly with a realistic, dimensionally checkable presentation.

    107 GitHub stars~1.6k tokensUpdated today
    Auto-check passed
  • Author Rhai Tests

    LunCoSim/lunco-sim

    Author and review LunCoSim behavioral, asset-backed, component, mission, visual, and requirements-verification tests.

    107 GitHub stars~3.5k tokensUpdated today
    Auto-check passed
  • Author Rhai Tool

    LunCoSim/lunco-sim

    Create, extend, register, or debug a reusable LunCoSim Rhai tool library for live USD authoring, component linting, inspection, or test support.

    107 GitHub stars~5.2k tokensUpdated today
    Auto-check passed
  • Author Tutorial

    LunCoSim/lunco-sim

    Author an interactive tutorial, guided lesson, onboarding flow, coach-mark tour, or objectives checklist in LunCoSim.

    107 GitHub stars~1.4k tokensUpdated today
    Auto-check passed

Questions about Author Usd Physics

What does Author Usd Physics do?

Author or diagnose LunCoSim USD physics: rigid bodies, colliders, joints, joint frames, drives, gravity, collision filtering, and scene teardown. Author Usd Physics is an agent skill from LunCoSim/lunco-sim. Author or diagnose LunCoSim USD physics: rigid bodies, colliders, joints, joint frames, drives, gravity, collision filtering, and scene teardown.

When should I use Author Usd Physics?

Author Usd Physics fits situations like: exploding vehicles; scene-owned physics state.

How do I install Author Usd Physics in Claude Code?

Run `npx skills add LunCoSim/lunco-sim --skill author-usd-physics -a claude-code`. Or copy the skill folder (skills/author-usd-physics in LunCoSim/lunco-sim) into .claude/skills/author-usd-physics in your project. Claude Code loads it when a task matches its description.

How do I install Author Usd Physics in Codex?

Run `npx skills add LunCoSim/lunco-sim --skill author-usd-physics -a codex`. Or copy the skill folder (skills/author-usd-physics in LunCoSim/lunco-sim) into .agents/skills/author-usd-physics in your project. Codex loads it when a task matches its description.

Can I use Author Usd Physics in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add LunCoSim/lunco-sim --skill author-usd-physics -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/author-usd-physics, .gemini/skills/author-usd-physics, .github/skills/author-usd-physics and .opencode/skills/author-usd-physics in your project.

What does Author Usd Physics need to run?

Going by SKILL.md and its folder, Author Usd Physics needs the command-line tools its instructions call (kind).

Does Author Usd Physics access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Author Usd Physics safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Author Usd Physics use?

Author Usd Physics is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Author Usd Physics use?

About 9.7k tokens (SKILL.md is roughly 39k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Author Usd Physics?

Skills that share tags, products or a category with Author Usd Physics: Hermes Agent Skill Authoring (NousResearch/hermes-agent, 252k stars), Diagnose Gateway (openclaw/openclaw, 392k stars), Configuring Oauth2 Authorization Flow (mukul975/Anthropic-Cybersecurity-Skills, 34k stars) and Authoring Skills (vercel/next.js, 143k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Author Usd Physics?

LunCoSim (a GitHub organization) maintains it in LunCoSim/lunco-sim, which has 107 GitHub stars. The repository holds 40 skills in this directory. The repository was last updated on October 10, 2026.

Source: LunCoSim/lunco-sim on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.