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 |
|---|---|---|---|---|
|
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. |
||
|
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: |
||
|
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: |
||
|
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. |
||
|
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 |
|
|
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. |
||
|
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. |
||
|
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: |
||
|
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. |
||
|
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. |
||
|
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. |
Fragile (16)#
Component |
Column |
Side |
Summary |
Asserted by |
|---|---|---|---|---|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
||
|
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 |
|
|
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 |
|
|
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 |
|---|---|---|---|---|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
consumer |
Absent column seeds 100000 (defaultCookedVoxelResolution) while the USD schema fallback published by the producer is 500000. Same class as maxConvexHulls. |
not asserted |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
consumer |
physxLimit: |
not asserted |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
consumer |
Absent column yields 0.0 instead of the header default 1.0, same pattern. Cooking input, not a PhysX default. |
not asserted |
|
|
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 |
|
|
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 |
|
|
consumer |
Silently cleared when solverType is PGS; the warning exists only on the USD path. |
not asserted |
|
|
consumer |
A value in [1, 32] that is not a power of two is silently replaced by 8 rather than rounded. |
not asserted |
|
|
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 |