Forces and coupling
Motors, solenoids, servos, thrusters and brakes all join electricity and motion. In CRUMB the circuit logic stays purely electrical, the component logic handles the physics, and the two pass plain numbers through UserData.
The coupling contract
Section titled “The coupling contract”A motor shows the whole pattern. Each half does its work in a set hook, and two numbers cross between them in UserData: the winding’s current one way and the shaft’s speed the other.
PreStep- Publish last tick’s current
- Back-EMF = Ke × speed
Step- Stamp the winding
PhysicsUpdate- Read the current
- Apply torque: Kt × current
- Publish the speed:
SpinRate
Three rules make this stable and repeatable:
- Publish from
PreStep, never fromStep.Stepruns on every solve pass, and the early passes carry trial values that can be absurd. The physics side reads the mailbox on its own thread, so it must only ever see a settled value. Publishing the previous tick’s current costs one tick, which is nothing at microsecond steps. - Couple in
PhysicsUpdate, neverUpdate. Feedback at frame rate is too slow for a light rotor, and the loop diverges. - Keep each constant on its own side. The back-EMF constant
Kebelongs to the winding, so pass it the raw spin rate. The torque constantKtbelongs to the component logic. Don’t applyKeon both sides.
The motor example is a complete part built this way.
Moving bodies
Section titled “Moving bodies”A mesh that carries a part body has .Rigidbody, a RigidbodyWrapper: Rendering.Wheel.Rigidbody for a mesh named Wheel. All values are SI, in world space unless they say otherwise.
Pushing
ApplyTorque(Vector3 axisTorque)in N·m, orApplyTorque(double)about the part’s hinge.ApplyForce(Vector3 force)in N, at the part’s centre of mass. A thruster or a solenoid is just a force.ApplyJointFriction(double nm)adds breakaway friction to the part’s joint, for a brake, a clutch or gearbox losses.
Applied values are sustained. CRUMB re-applies the last torque or force every physics step until you change it, so set it when it changes and set it to zero to stop.
Reading motion
AngularVelocityandLinearVelocity.SpinRate(rad/s) andHingeAngle(rad, multi-turn) about the hinge. These are the reads for back-EMF, encoders and feedback pots.HingeAxis, the hinge axis in world space.
Reading loads
JointForce,JointForceLocalandJointTorque: what the part’s joint carried over the last step, such as a load cell or torque sensor reading.ContactForce,ContactNormalForce,ContactTorque,ContactPoint,ContactArea,FrictionUseandInContact: what touching the world did to the part, for force sensors, bump switches and grip sensing.Weight: the body’s mass times the gravity it feels.
Inertia through a gearbox. Set ReflectedInertia (and optionally ReflectedInertiaGearRatio) once in Initialize for a geared drive. CRUMB gives it a real rotor, so a light geared part stays stable without you tuning its mass.
Soft surfaces and welded parts
Section titled “Soft surfaces and welded parts”A body’s physics config can make its contact surface soft: a rubber layer on a rigid backing, a solid material, or a hollow body. A soft surface squashes, spreads its load, grips and returns a known share of an impact. Use the material’s own figures (modulus or Shore hardness, resilience, friction) rather than values tuned to make a demo work.
Parts welded together act as one rigid body, and each part keeps its own mass, centre of mass and colliders. Forces still act where each part is, so a thruster on a welded wing turns the whole body as a real one would.
Turning a force into a reading
Section titled “Turning a force into a reading”A sensor that responds to force, such as a force-sensing resistor, reads the force in component logic and passes the circuit a resistance, never a force:
public override void PhysicsUpdate(double delta){ double force = Rendering.Sensor.Rigidbody.ContactNormalForce; // N, over the last physics step Circuit.UserData[0] = OhmsFromCurve(force); // the circuit only ever sees ohms}The circuit logic latches UserData[0] in PreStep and stamps StampConductanceDynamic(a, b, 1.0 / Math.Max(1.0, ohms)) in Step.

