Skip to content

Component logic

Component logic reads what the circuit is doing and acts on the world: it lights a mesh, turns a knob, draws a screen, applies a torque, casts a ray or responds to a click. It works with your model through members CRUMB generates from your markers, named after the nodes in your model.

As with circuit logic, you type fields, hooks and helper methods, never a class. Everything on BaseComponentLogic, such as Circuit, SaveState and RequireRender, is yours to use by name. Every hook is optional: write only the ones you use. See What you write.

// An LED: brightness follows the averaged current the circuit logic recorded.
public override void Update(double delta)
{
float glow = (float)Math.Clamp(Circuit.GetDisplayAverage(0) / Properties.RatedAmps, 0.0, 1.0);
Rendering.Lens.Material.EmissionEnergyMultiplier = glow * Properties.MaxGlow;
}
Hook When Use it for
Initialize Once per instance, after properties load (also after Reset State) Set up state, subscribe to clicks and drags, declare body settings such as ReflectedInertia, and restore visuals from saved state.
Update(delta) Every rendered frame, on the main thread Visuals: lights, displays, moving needles, anything you look at. While the simulation plays, delta is simulated time. It doesn’t run while paused.
PhysicsUpdate(delta) Every physics step (1 kHz by default), only while the circuit runs Forces, body reads that feed back into the circuit, and world sensing with Cast.
OnPropertyUpdated(name) When one of this part’s properties is edited (and once after load, with null) Re-derive values from properties.
OnCircuitPropertyUpdated(name) When a circuit logic property is edited Cache circuit-side constants you need.

PhysicsUpdate runs on the physics thread, and every member of your model is safe to use from it. Each step sees the circuit held at that moment in simulated time, so a run repeats exactly at any speed.

Circuit is your part’s circuit logic. Everything public on it is available:

  • Circuit.UserData[k]: the 16 shared numbers. Write your inputs for the circuit here and read its outputs.
  • Circuit.GetCurrent(slot), GetConnectionVoltage(i) and GetVoltageDelta(a, b): currents and voltages.
  • Circuit.GetDisplayAverage(slot): averaged brightness for LEDs and segments.
  • Circuit.FramebufferVersion and Circuit.ReadFramebuffer(...): pixels for screens.
  • Circuit.Failed and Circuit.FailureReason.

It only works one way: circuit logic never sees component logic.

CRUMB generates one member for each node you marked, so the names in your code are the names in your model. The Model and world reference lists every type.

Every node under Rendering is a TransformNodeWrapper:

  • Position, Rotation (radians), RotationDegrees and Scale, as Vector3.
  • Visible hides or shows the node and everything under it. It isn’t saved, so set it in Initialize from the part’s own saved state.
  • Sensing: Cast, CastTo and SampleField. See Sensing the world.

A mesh is a GeometryNodeWrapper and adds:

  • .Material: AlbedoColor, Metallic, Roughness, Emission and so on for a standard material, or one property per uniform for your own shader. Writing the same value again costs nothing.
  • .Display: a DisplaySurface for screens. SetPixels shows an RGB565 frame as emitted light (a backlit LCD or OLED), and SetReflectedPixels shows one lit by the room (e-ink, or a transflective LCD).
  • .TextDecal: a TextDecalSurface for printed text.
  • .Rigidbody: a RigidbodyWrapper, when the mesh carries a part body. See Forces and coupling.

Special markers add their own members: a FieldEmitterWrapper radiates a named field, an ImageSensorWrapper takes pictures, and a ComponentLightWrapper is a real light. Smoke, spark and sound markers can be moved and aimed, but only a failure plays them.

  • Connections.<Wire>: a WireConnectionWrapper with WireColor, SheathColor, HasSheath and WireLength.
  • Interactivity.<Zone>: an InteractivityZoneWrapper with MouseDown, MouseUp, DragStart, Drag and DragEnd events. Subscribe in Initialize.
  • Properties.<Name>: this part’s properties.
  • CircuitProperties.<Name>: a read-only view of the circuit logic’s properties, safe to read from any thread.

Positions, directions and colours use CRUMB’s own Vector2, Vector3, Vector4 and Color.

This follows the pattern the shipped parts use: flip the property, save it, and redraw. On is a bool property on the component logic.

public override void Initialize()
{
Interactivity.Toggle.MouseDown += OnToggle;
Show(); // from the saved position
}
private void OnToggle(Vector2 at)
{
Properties.On = !Properties.On;
SaveState("On", Properties.On); // survives Reset State and reopening the file
Show();
RequireRender();
}
// An inspector edit moves the lever too.
public override void OnPropertyUpdated(string propertyName) => Show();
private void Show()
{
Rendering.Lever.RotationDegrees = new Vector3(Properties.On ? 20f : -20f, 0f, 0f);
if (Circuit != null) Circuit.UserData[0] = Properties.On ? 1.0 : 0.0; // latched by the circuit in PreStep
}

On Reset State a new instance runs over the same nodes. Anything you moved or hid isn’t put back for you, so set your visuals in Initialize from saved state, and declare body settings such as ReflectedInertia there each time.

Component logic is checked against a strict whitelist before it compiles. Every type and namespace your code names must be on it. Anything else is refused: the editor marks the line with an error, and a part that breaks the rules won’t load, whether it comes from a save file or the Workshop.

Component logic may use:

  • Everything in the Simulation namespace, the same as circuit logic.
  • Everything in the CRUMB.Script.Component.Logic namespace: BaseComponentLogic and every type on Model and world.
  • bool, byte, sbyte, short, ushort, int, uint, long, ulong, char, float, double, decimal, string and object.
  • IntPtr and UIntPtr.
  • List<T>, nullable values such as double?, and System.Math.
  • Arrays of any of these.
  • Types you declare yourself, such as a small struct that holds one sample.

The check covers type arguments too: List<Vector3> is allowed, and a list of anything off the whitelist isn’t.

Common things that aren’t on it: Dictionary, HashSet, tuples, Random, MathF, Func and Action, exception types and LINQ.

The exact lists, read from CRUMB’s source, are on The whitelist in the API reference.

Error What it means
CRUMB002 Your code names a namespace that isn’t on the whitelist.
CRUMB003 Your code names a type that isn’t on the whitelist. The message lists what is allowed.