Recorded findings#

Every divergence the contract records between the USD schema, ovstage population (the producer) and the ovphysx parser (the consumer). A defect is asserted as a canary when a composition’s absent-column parity case observes the column and would fail the day the consumer is fixed without the contract being updated; a defect no composition observes is documentation only. Fragile spots and notes are never asserted.

Defects (11)#

Component

Column

Side

Summary

Asserted by

Cube

size

consumer

Absent size resolves to 1.0 on the consumer but USD population always publishes the resolved fallback 2.0. Capsule, Cylinder and Cone seed the USD fallbacks in the consumer; Cube does not. Only a hand-populated stage that omits the column is affected.

rigid_body_cube

PhysicsPrismaticJoint

physics:lowerLimit

consumer

Absent lowerLimit/upperLimit columns leave the bounds at 0 and the OR-form enable rule activates a [0, 0] limit, locking the slider. Masked on producer-populated stages, which publish -inf/inf. The active limit also overwrites the PhysX default bounceThreshold of the untouched limit (0.5 for angular and cone limits, 2 * tolerances length for linear limits) with the PhysxLimitAPI value or 0, so physxLimit::bounceThreshold diverges too (observed: 0 vs 0.5 / 200).

prismatic_joint_pair

PhysicsRevoluteJoint

physics:lowerLimit

consumer

An absent lowerLimit/upperLimit column leaves the JointInfo bound at 0, and the OR-form enable rule then activates the limit at [0, 0], locking the hinge. USD population always publishes the resolved -inf/inf so the defect is masked there; only a hand-populated stage that omits the columns is affected. The active limit also overwrites the PhysX default bounceThreshold of the untouched limit (0.5 for angular and cone limits, 2 * tolerances length for linear limits) with the PhysxLimitAPI value or 0, so physxLimit::bounceThreshold diverges too (observed: 0 vs 0.5 / 200).

revolute_joint_pair

PhysicsScene

physics:gravityMagnitude

consumer

An authored PhysicsScene with no gravity columns gets the consumer’s unscaled SI defaults (9.81 along -Z) instead of the units- and up-axis-aware values both the producer and the consumer’s synthetic default scene produce. The scene walker copies the columns into a default-constructed SceneInfo and the parser overwrites the units-aware overlay with it. The raw fallbacks (0,0,0) and -inf are also not resolved on the consumer. Masked on producer-populated stages because the producer always resolves them.

physics_scene

PhysicsSphericalJoint

physics:coneAngle0Limit

consumer

Absent coneAngle0Limit/coneAngle1Limit columns leave both half-angles at 0, which satisfies the non-negative AND rule and activates a zero cone. PhysX rejects it: PxJointLimitCone::isValid requires 0 < angle < pi, so PxD6Joint::setSwingLimit reports ‘limit invalid’ through the error callback and the swing limit is not applied (the contract test bootstrap treats the error as fatal, which is why spherical_joint_pair authors both angles in every variant). Masked on producer-populated stages, which publish -1 and leave the swing axes free with the PhysX default cone (bounceThreshold 0.5).

documentation only

PhysxPhysicsGearJoint

physics:gearRatio

consumer

The descriptor default for gearRatio is 0 while the schema fallback is 1; an unpublished column yields a zero ratio instead of the identity coupling USD population produces. Only a hand-populated stage that omits the column is affected.

gear_joint_pair

PhysxPhysicsRackAndPinionJoint

physics:ratio

consumer

The descriptor default for ratio is 0 while the schema fallback is 1; an unpublished column yields a zero ratio instead of the identity coupling USD population produces. Only a hand-populated stage that omits the column is affected.

rack_and_pinion_joint_pair

PhysicsLimitAPI

limit:<inst>:physics:low

consumer

With an instance applied but its low/high columns absent, both bounds stay 0 and the OR-form enable rule activates a [0, 0] limit that locks the axis, whereas USD population publishes -inf/inf and leaves the axis free. Only a hand-populated stage that omits the columns is affected. The active limit also overwrites the PhysX default bounceThreshold of the untouched limit (0.5 for angular and cone limits, 2 * tolerances length for linear limits) with the PhysxLimitAPI value or 0, so physxLimit::bounceThreshold diverges too (observed: 0 vs 0.5 / 200).

d6_joint_pair

PhysicsMaterialAPI

physics:staticFriction

consumer

The consumer default 0.5 for both frictions disagrees with the schema fallback 0.0 that the producer publishes: a producer-populated Material simulates with friction 0, a hand-populated stage that omits the columns with 0.5. Observed in the material_bound_cube parity test (PxMaterial::getStaticFriction 0.5 vs 0). Same for physics:dynamicFriction.

material_bound_cube

PhysxArticulationAPI

physxArticulation:stabilizationThreshold

consumer

The parser’s units-scaled default uses coefficient 1e-2 while the schema documents and falls back to 1e-5 (and PhysxRigidBodyAPI uses 1e-5): a 1000x divergence beyond the units factor, frozen in by TestParseArticulation. sleepThreshold’s 5e-5 matches the schema.

articulation_fixed_base, articulation_floating

PhysxArticulationAPI

physxArticulation:sleepThreshold

both

The producer performs no units-scaled rewrite for articulations (it does for rigid bodies) and the consumer has no authored gate, so a producer-populated stage with PhysxArticulationAPI applied and unauthored thresholds simulates with the raw 5e-5 / 1e-5 while a stage that omits the columns gets 5e-3 / 1.0 on a meter stage. The two population paths disagree on every stage; the gap grows with 1/metersPerUnit^2.

articulation_fixed_base, articulation_floating

Fragile (16)#

Component

Column

Side

Summary

Asserted by

PhysicsDistanceJoint

physics:localRot0

consumer

The PhysxJointDesc constructor default for localPose0Orientation/localPose1Orientation is {1, 0, 0, 0} in the (x, y, z, w) layout, a 180 degree rotation about X rather than identity. Masked today because copyBaseFields always overwrites it from JointInfo, whose default {0, 0, 0, 1} is identity. Any future path that builds a joint descriptor without copyBaseFields inherits a flipped frame.

not asserted

PhysicsFixedJoint

physics:localRot0

consumer

The PhysxJointDesc constructor default for localPose0Orientation/localPose1Orientation is {1, 0, 0, 0} in the (x, y, z, w) layout, a 180 degree rotation about X rather than identity. Masked today because copyBaseFields always overwrites it from JointInfo, whose default {0, 0, 0, 1} is identity. Any future path that builds a joint descriptor without copyBaseFields inherits a flipped frame.

not asserted

PhysicsJoint

physics:localRot0

consumer

The PhysxJointDesc constructor default for localPose0Orientation/localPose1Orientation is {1, 0, 0, 0} in the (x, y, z, w) layout, a 180 degree rotation about X rather than identity. Masked today because copyBaseFields always overwrites it from JointInfo, whose default {0, 0, 0, 1} is identity. Any future path that builds a joint descriptor without copyBaseFields inherits a flipped frame.

not asserted

PhysicsPrismaticJoint

physics:localRot0

consumer

The PhysxJointDesc constructor default for localPose0Orientation/localPose1Orientation is {1, 0, 0, 0} in the (x, y, z, w) layout, a 180 degree rotation about X rather than identity. Masked today because copyBaseFields always overwrites it from JointInfo, whose default {0, 0, 0, 1} is identity. Any future path that builds a joint descriptor without copyBaseFields inherits a flipped frame.

not asserted

PhysicsRevoluteJoint

physics:localRot0

consumer

The PhysxJointDesc constructor default for localPose0Orientation/localPose1Orientation is {1, 0, 0, 0} in the (x, y, z, w) layout, a 180 degree rotation about X rather than identity. Masked today because copyBaseFields always overwrites it from JointInfo, whose default {0, 0, 0, 1} is identity. Any future path that builds a joint descriptor without copyBaseFields inherits a flipped frame.

not asserted

PhysicsRevoluteJoint

physics:lowerLimit

consumer

Limit enable differs between backends for revolute joints: the ovstage walker enables the limit when EITHER bound is finite and inside +-0.5e38 (OR), the USD walker requires BOTH (AND). A revolute with lowerLimit -30 and upperLimit inf is limited on ovstage and free on USD. Prismatic and D6 use OR on both backends.

not asserted

PhysicsSphericalJoint

physics:localRot0

consumer

The PhysxJointDesc constructor default for localPose0Orientation/localPose1Orientation is {1, 0, 0, 0} in the (x, y, z, w) layout, a 180 degree rotation about X rather than identity. Masked today because copyBaseFields always overwrites it from JointInfo, whose default {0, 0, 0, 1} is identity. Any future path that builds a joint descriptor without copyBaseFields inherits a flipped frame.

not asserted

PhysxPhysicsGearJoint

physics:localRot0

consumer

The PhysxJointDesc constructor default for localPose0Orientation/localPose1Orientation is {1, 0, 0, 0} in the (x, y, z, w) layout, a 180 degree rotation about X rather than identity. Masked today because copyBaseFields always overwrites it from JointInfo, whose default {0, 0, 0, 1} is identity. Any future path that builds a joint descriptor without copyBaseFields inherits a flipped frame.

not asserted

PhysxPhysicsRackAndPinionJoint

physics:localRot0

consumer

The PhysxJointDesc constructor default for localPose0Orientation/localPose1Orientation is {1, 0, 0, 0} in the (x, y, z, w) layout, a 180 degree rotation about X rather than identity. Masked today because copyBaseFields always overwrites it from JointInfo, whose default {0, 0, 0, 1} is identity. Any future path that builds a joint descriptor without copyBaseFields inherits a flipped frame.

not asserted

PhysxContactReportAPI

physxContactReport:threshold

consumer

Because ovstage reports every column as authored, the consumer distinguishes authored from unauthored by comparing against the raw schema fallback 1.0; an authored 1.0 collapses to the units-scaled default (1 / metersPerUnit). Same shape as the character controller contactOffset/stepOffset case.

not asserted

PhysxJointAxisAPI

physxJointAxis:<inst>:armature

consumer

Only a strictly positive per-axis armature displaces the joint-level seed, because 0 is both the fallback and a legal authored value and the authored bit is unavailable on ovstage. Consequence: an explicit per-axis armature of 0 cannot override a non-zero physxJoint:armature. Documented and accepted (ADR-0002 invariant 1).

not asserted

PhysxLimitAPI

physxLimit:<inst>:restitution

consumer

Restitution is clamped to [0, FLT_MAX] although the schema documents [0, 1] and PxJointLimitParameters::isValid requires restitution <= 1; a value above 1 produces an invalid PhysX limit instead of being clamped.

not asserted

PhysxRigidBodyAPI

physxRigidBody:sleepThreshold

The authored-bit gate that protects units-scaled defaults is a no-op on ovstage (hasAuthoredAttribute is always true), so the default is passed through the clamp. Every current default lies inside its clamp range; a future default outside it would be clipped on ovstage only. Same for stabilizationThreshold and maxDepenetrationVelocity.

not asserted

PhysxSceneAPI

physxScene:bounceThreshold

consumer

The consumer default 1e-6 is not units-scaled while every neighbouring length default is, and the raw schema fallback 0 is clamped up to it. A centimeter stage therefore bounces on contacts 100 times slower (relative to its units) than a meter stage would.

not asserted

PhysxSceneAPI

physxScene:timeStepsPerSecond

consumer

The authored-bit gates (readClampedIfAuthored, readBoolIfAuthored) are no-ops on ovstage where every column reads as authored, so the resolved value always wins and a value equal to the raw fallback is indistinguishable from unauthored. This is what makes the newton:timeStepsPerSecond fallback unreachable on a producer-populated stage: the published 60 counts as authored.

not asserted

PhysxSceneAPI

physxScene:maxBiasCoefficient

consumer

Whether the authored value takes effect depends on the solver configuration, not on the stage: PhysX applies PxSceneDesc::maxBiasCoefficient in the PGS and GPU dynamics contexts but the CPU TGS context constructs with PX_MAX_F32 (physx DyTGSDynamics.cpp), so with solverType TGS and enableGPUDynamics false the column is silently ignored and PxScene::getMaxBiasCoefficient returns FLT_MAX. Observed in the population contract test (authored 10, read FLT_MAX).

not asserted

Notes (36)#

Component

Column

Side

Summary

Asserted by

PhysicsCollisionGroup

physics:invertFilteredGroups

consumer

Declared by the schema, prefetched by the walker and used as the enumeration probe column, but never read into any descriptor: authoring it has no effect. Only the scene-level physxScene:invertCollisionGroupFilter changes the filtering sense. Not a column of this contract for that reason.

not asserted

PhysicsDistanceJoint

physics:breakForce

consumer

The ovstage walker seeds breakForce/breakTorque with FLT_MAX while the USD walker leaves the schema fallback inf, so the descriptor differs bit-for-bit between backends when the columns are unauthored. No simulation difference: the runtime maps any non-finite value to FLT_MAX before calling PxJoint::setBreakForce.

not asserted

PhysicsDistanceJoint

physics:localRot0

producer

Quaternion lanes are published (x, y, z, w) although USD authors (w, x, y, z); the consumer reads the lanes verbatim. A hand populator that copies the USD tuple order produces a wrong frame with no diagnostic. Also applies to physics:localRot1.

not asserted

PhysicsFixedJoint

physics:breakForce

consumer

The ovstage walker seeds breakForce/breakTorque with FLT_MAX while the USD walker leaves the schema fallback inf, so the descriptor differs bit-for-bit between backends when the columns are unauthored. No simulation difference: the runtime maps any non-finite value to FLT_MAX before calling PxJoint::setBreakForce.

not asserted

PhysicsFixedJoint

physics:localRot0

producer

Quaternion lanes are published (x, y, z, w) although USD authors (w, x, y, z); the consumer reads the lanes verbatim. A hand populator that copies the USD tuple order produces a wrong frame with no diagnostic. Also applies to physics:localRot1.

not asserted

PhysicsJoint

physics:breakForce

consumer

The ovstage walker seeds breakForce/breakTorque with FLT_MAX while the USD walker leaves the schema fallback inf, so the descriptor differs bit-for-bit between backends when the columns are unauthored. No simulation difference: the runtime maps any non-finite value to FLT_MAX before calling PxJoint::setBreakForce.

not asserted

PhysicsJoint

physics:localRot0

producer

Quaternion lanes are published (x, y, z, w) although USD authors (w, x, y, z); the consumer reads the lanes verbatim. A hand populator that copies the USD tuple order produces a wrong frame with no diagnostic. Also applies to physics:localRot1.

not asserted

PhysicsPrismaticJoint

physics:breakForce

consumer

The ovstage walker seeds breakForce/breakTorque with FLT_MAX while the USD walker leaves the schema fallback inf, so the descriptor differs bit-for-bit between backends when the columns are unauthored. No simulation difference: the runtime maps any non-finite value to FLT_MAX before calling PxJoint::setBreakForce.

not asserted

PhysicsPrismaticJoint

physics:localRot0

producer

Quaternion lanes are published (x, y, z, w) although USD authors (w, x, y, z); the consumer reads the lanes verbatim. A hand populator that copies the USD tuple order produces a wrong frame with no diagnostic. Also applies to physics:localRot1.

not asserted

PhysicsRevoluteJoint

physics:breakForce

consumer

The ovstage walker seeds breakForce/breakTorque with FLT_MAX while the USD walker leaves the schema fallback inf, so the descriptor differs bit-for-bit between backends when the columns are unauthored. No simulation difference: the runtime maps any non-finite value to FLT_MAX before calling PxJoint::setBreakForce.

not asserted

PhysicsRevoluteJoint

physics:localRot0

producer

Quaternion lanes are published (x, y, z, w) although USD authors (w, x, y, z); the consumer reads the lanes verbatim. A hand populator that copies the USD tuple order produces a wrong frame with no diagnostic. Also applies to physics:localRot1.

not asserted

PhysicsScene

physics:gravityDirection

producer

The producer requirements describe replacing a ZERO direction with negative up axis; the code replaces an UNAUTHORED direction and keeps an authored zero. Documented here as the code behaves.

not asserted

PhysicsSphericalJoint

physics:breakForce

consumer

The ovstage walker seeds breakForce/breakTorque with FLT_MAX while the USD walker leaves the schema fallback inf, so the descriptor differs bit-for-bit between backends when the columns are unauthored. No simulation difference: the runtime maps any non-finite value to FLT_MAX before calling PxJoint::setBreakForce.

not asserted

PhysicsSphericalJoint

physics:localRot0

producer

Quaternion lanes are published (x, y, z, w) although USD authors (w, x, y, z); the consumer reads the lanes verbatim. A hand populator that copies the USD tuple order produces a wrong frame with no diagnostic. Also applies to physics:localRot1.

not asserted

PhysxPhysicsGearJoint

physics:breakForce

consumer

The ovstage walker seeds breakForce/breakTorque with FLT_MAX while the USD walker leaves the schema fallback inf, so the descriptor differs bit-for-bit between backends when the columns are unauthored. No simulation difference: the runtime maps any non-finite value to FLT_MAX before calling PxJoint::setBreakForce.

not asserted

PhysxPhysicsGearJoint

physics:localRot0

producer

Quaternion lanes are published (x, y, z, w) although USD authors (w, x, y, z); the consumer reads the lanes verbatim. A hand populator that copies the USD tuple order produces a wrong frame with no diagnostic. Also applies to physics:localRot1.

not asserted

PhysxPhysicsGearJoint

physics:localPos0

consumer

Authored local frames (physics:localPos0/1, physics:localRot0/1) are ignored: the runtime replaces the gear joint’s local frames with the referenced hinge joints’ frames. The frames are therefore unobservable on this type in the contract tests.

not asserted

PhysxPhysicsRackAndPinionJoint

physics:breakForce

consumer

The ovstage walker seeds breakForce/breakTorque with FLT_MAX while the USD walker leaves the schema fallback inf, so the descriptor differs bit-for-bit between backends when the columns are unauthored. No simulation difference: the runtime maps any non-finite value to FLT_MAX before calling PxJoint::setBreakForce.

not asserted

PhysxPhysicsRackAndPinionJoint

physics:localRot0

producer

Quaternion lanes are published (x, y, z, w) although USD authors (w, x, y, z); the consumer reads the lanes verbatim. A hand populator that copies the USD tuple order produces a wrong frame with no diagnostic. Also applies to physics:localRot1.

not asserted

PhysxPhysicsRackAndPinionJoint

physics:localPos0

consumer

Authored local frames (physics:localPos0/1, physics:localRot0/1) are ignored: the runtime replaces the rack-and-pinion joint’s local frames with the referenced hinge and prismatic joints’ frames. The frames are therefore unobservable on this type in the contract tests.

not asserted

PhysicsArticulationRootAPI

usd-schemas

consumer

Nested articulation roots (a root prim inside another root’s subtree) are rejected with an error and skipped. A root with nothing aggregable still emits one default articulation descriptor anchored on the root prim.

not asserted

PhysxConvexDecompositionCollisionAPI

physxConvexDecompositionCollision:maxConvexHulls

consumer

Absent column seeds 16 (PhysxCookingParams.h defaultCookedMaxHullCount) while the USD schema fallback published by the producer is 32. Only a hand-populated stage that omits the column is affected; it is a cooking input, not a PhysX default, so the divergence changes the cooked hull count rather than a PhysX object field.

not asserted

PhysxConvexDecompositionCollisionAPI

physxConvexDecompositionCollision:voxelResolution

consumer

Absent column seeds 100000 (defaultCookedVoxelResolution) while the USD schema fallback published by the producer is 500000. Same class as maxConvexHulls.

not asserted

PhysxDrivePerformanceEnvelopeAPI

physxDrivePerformanceEnvelope:<inst>:maxActuatorVelocity

consumer

The rotational branch is instance != linear, so angular, rotX, rotY, rotZ and any unrecognised instance name receive the degrees-to-radians conversions. Unlike PhysxJointAxisAPI, whose conversions are selected explicitly per joint type, an unknown instance here is silently treated as rotational.

not asserted

PhysxJointAPI

physxJoint:maxJointVelocity

consumer

Authored-ness is decided by the value (v < FLT_MAX), because the authored bit is unavailable on ovstage. An explicitly authored inf therefore behaves as unauthored and yields to newton:velocityLimit when that is present.

not asserted

PhysxLimitAPI

physxLimit:<inst>:bounceThreshold

consumer

Angular instances declare deg/s but no conversion is applied; PhysX reads the value as rad/s. Same on both backends, so parity is unaffected.

not asserted

PhysxLimitAPI

physxLimit:<inst>:restitution

consumer

physxLimit::contactDistance is declared by the schema but is neither in the walker’s column bucket nor in readPhysxLimitApi; it is never read.

not asserted

PhysxPhysicsDistanceJointAPI

physxPhysicsDistanceJoint:springEnabled

consumer

The stage-wide presence flag present.physxDistanceJoint is computed by the walker but never consulted by the parser, which goes straight to the per-prim hasSchema probe. Dead performance gate, no correctness effect.

not asserted

PhysxSDFMeshCollisionAPI

physxSDFMeshCollision:sdfNarrowBandThickness

consumer

Absent column yields 0.0 instead of the header default 0.01 because the parser re-seeds a local to 0.0 and assigns it back unconditionally. USD population publishes the schema fallback 0.01, so only a hand-populated stage that omits the column is affected. Cooking input, not a PhysX default.

not asserted

PhysxSDFMeshCollisionAPI

physxSDFMeshCollision:sdfMargin

consumer

Absent column yields 0.0 instead of the header default 0.01, same pattern as sdfNarrowBandThickness. Cooking input, not a PhysX default.

not asserted

PhysxSDFMeshCollisionAPI

physxSDFMeshCollision:sdfTriangleCountReductionFactor

consumer

Absent column yields 0.0 instead of the header default 1.0, same pattern. Cooking input, not a PhysX default.

not asserted

PhysxSDFMeshCollisionAPI

physxSDFMeshCollision:sdfResolution

consumer

Absent column yields 0 and disables the SDF entirely (the field falls back to triangle-mesh cooking) instead of the header default 256. USD population publishes 256. Deliberate validity probe in the parser; recorded so a hand populator knows the column is effectively mandatory.

not asserted

PhysxSceneAPI

physxScene:minPositionIterationCount

consumer

Raw schema fallback 1 versus consumer absent default 0: only bodies authored with 0 position iterations observe the difference. When min exceeds max (either pair) min is silently clamped down to max; the diagnostic is emitted by the USD schema parser only, so an ovstage-populated stage gets no warning.

not asserted

PhysxSceneAPI

physxScene:enableExternalForcesEveryIteration

consumer

Silently cleared when solverType is PGS; the warning exists only on the USD path.

not asserted

PhysxSceneAPI

physxScene:gpuMaxNumPartitions

consumer

A value in [1, 32] that is not a power of two is silently replaced by 8 rather than rounded.

not asserted

PhysxSphereFillCollisionAPI

physxSphereFillCollision:voxelResolution

consumer

Absent column seeds 100000 (PhysxCookingParams.h defaultCookedVoxelResolution, shared with convex decomposition) while the USD schema fallback published by the producer is 50000. Only a hand-populated stage that omits the column is affected. Cooking input, not a PhysX default.

not asserted