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;}When it runs
Section titled “When it runs”| 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.
Reading the circuit
Section titled “Reading the circuit”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)andGetVoltageDelta(a, b): currents and voltages.Circuit.GetDisplayAverage(slot): averaged brightness for LEDs and segments.Circuit.FramebufferVersionandCircuit.ReadFramebuffer(...): pixels for screens.Circuit.FailedandCircuit.FailureReason.
It only works one way: circuit logic never sees component logic.
Your model in code
Section titled “Your model in code”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.
Rendering.<Node>
Section titled “Rendering.<Node>”Every node under Rendering is a TransformNodeWrapper:
Position,Rotation(radians),RotationDegreesandScale, asVector3.Visiblehides or shows the node and everything under it. It isn’t saved, so set it inInitializefrom the part’s own saved state.- Sensing:
Cast,CastToandSampleField. See Sensing the world.
A mesh is a GeometryNodeWrapper and adds:
.Material:AlbedoColor,Metallic,Roughness,Emissionand so on for a standard material, or one property per uniform for your own shader. Writing the same value again costs nothing..Display: aDisplaySurfacefor screens.SetPixelsshows an RGB565 frame as emitted light (a backlit LCD or OLED), andSetReflectedPixelsshows one lit by the room (e-ink, or a transflective LCD)..TextDecal: aTextDecalSurfacefor printed text..Rigidbody: aRigidbodyWrapper, 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.
Everything else
Section titled “Everything else”Connections.<Wire>: aWireConnectionWrapperwithWireColor,SheathColor,HasSheathandWireLength.Interactivity.<Zone>: anInteractivityZoneWrapperwithMouseDown,MouseUp,DragStart,DragandDragEndevents. Subscribe inInitialize.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.
A switch that remembers its position
Section titled “A switch that remembers its position”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}Saving and redrawing
Section titled “Saving and redrawing”SaveState(name, value)persists abool,intordoubleinto the placed part, without pausing the simulation.MarkDesignDirty()marks the design as changed.RequireRender()asks for a redraw when something has changed that doesn’t redraw by itself.
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.
The whitelist
Section titled “The whitelist”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
Simulationnamespace, the same as circuit logic. - Everything in the
CRUMB.Script.Component.Logicnamespace:BaseComponentLogicand every type on Model and world. bool,byte,sbyte,short,ushort,int,uint,long,ulong,char,float,double,decimal,stringandobject.IntPtrandUIntPtr.List<T>, nullable values such asdouble?, andSystem.Math.- Arrays of any of these.
- Types you declare yourself, such as a small
structthat 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. |

