Decay

Decay adds an amount to its output on command, holds it, then ramps it linearly back to a resting level.
3.30–3.34

Decay adds an amount to its output on command, holds it, then ramps it linearly back to a resting level. Use it for an event-driven offset that should fade on its own — a nudge after a collision, a kick on a jog command, a temporary allowance that expires. The ramp is straight, not exponential: despite the name, there is no time constant.

The amount accumulates. A second command before the first has finished adds to what is already there and restarts the hold.

flowchart LR
    i1(["input — amount to add"]) --> B["Decay"]
    i2(["set"]) --> B
    i3(["reset"]) --> B
    B --> o1(["output — the decaying value"])
    B --> o2(["decaySlope — fall per cycle"])
    B --> o3(["busy"])

After a set, output jumps by input, holds for decayStartTimeSec, then falls to defaultValue over decayDurationSec and stops. The fall rate is $($ height above defaultValue $) / ($ decayDurationSec $)$ per second, and it is fixed at the moment of the set. Total event length is decayStartTimeSec + decayDurationSec.

This block has no enable, no disable and no isEnabled. It is always running, and while idle it drives output to defaultValue on every cycle — nothing else can hold that path at another value. decayDurationSec must never be 0; see Limits and errors.

Signals

Inputs

Path Unit Range Description
input signal unit unbounded The amount to add to output, one element per channel. It is a magnitude, not a signal — it is read only at the moment of a set, and changing it at any other time has no effect.
set - - A rising value starts an event: input is added to output, the hold begins, and busy goes true. The block clears this input itself, so it is a one-shot. Do not drive it from a link that writes every cycle — the block would fire continuously.
reset - - While true, output is forced to defaultValue and any event in progress is abandoned as a step, not a ramp. This input is a level, not a pulse — the block does not clear it, so holding it true pins the output.

Outputs

Path Unit Description
output signal unit The decaying value, one element per channel. Equals defaultValue whenever no event is in progress, and is rewritten every cycle while idle. Starts at defaultValue after every controller start.
decaySlope signal unit per cycle How much output falls each cycle during the ramp, one element per channel. Fixed when the event starts and unchanged for its duration. Use it to confirm the event was set up as you intended.
busy - True from the moment of a set until the ramp has finished. A single value for the whole block, not one per channel. It stays true for decayStartTimeSec + decayDurationSec.

Parameters

Path Unit Default Range Effect
defaultValue signal unit 0.0 any The resting level the output returns to, one element per channel. The output is driven here whenever no event is in progress.
decayStartTimeSec s 0.0 0 upward How long output holds at its full height before the ramp begins. 0 starts the ramp immediately. Not checked — a negative value makes the event never finish.
decayDurationSec s 1.0 must be greater than one task period How long the ramp takes. Longer is gentler. Not checked, and 0 breaks the block — see Limits and errors.

All three parameters are persistent and survive a controller restart. The inputs and outputs do not. No parameters exist below this block.

Setup

  1. Set defaultValue to the level the output should rest at — usually 0.

  2. Set decayDurationSec to a real value before anything else.

    Never leave decayDurationSec at 0. A zero duration makes output and decaySlope go to a non-numeric value on the first set, and only a reset clears it. Check this before you wire set to anything.

  3. Set decayStartTimeSec to the hold you want, or 0 for none.

  4. Set input to the amount you want added. Confirm output does not move — the amount is only read on a set.

  5. Pulse set true for one cycle. output should jump by input, busy should go true, and decaySlope should read the amount divided by the duration in cycles.

  6. Watch output hold, then fall in a straight line to defaultValue. Time the whole event: it should equal decayStartTimeSec + decayDurationSec.

  7. Confirm busy goes false at the end and output sits exactly on defaultValue.

  8. Trace every element of output. All channels share one schedule, so they must start and finish together.

Tuning

  1. Set the amount first, with input, and confirm the height of the jump on a trace of output. That height is what the consumer downstream has to tolerate.
  2. Set decayStartTimeSec from the process, not from feel: it is how long the offset must stay at full strength to do its job.
  3. Set decayDurationSec from what the machine can absorb. Read decaySlope after a set and check that fall rate against whatever consumes output — a rate that is too steep shows up as a bump downstream.
  4. Work out how often your event source can fire. If it can fire faster than decayStartTimeSec + decayDurationSec, the amounts will stack and there is no upper limit on how high output goes. Either lengthen the gap or bound the value downstream.
  5. Re-check nothing after a task-rate change. Both durations are in seconds, so they hold their meaning — unlike the sample-counted blocks.

One set event with a 0.2 s hold at three decay durations: all three hold at1.0 for 0.2 s, then fall in straight lines reaching 0 at 0.4 s, 0.7 s and1.2 s.

Read the fall rate off any curve as its slope during the ramp.

Symptom Cause Action
output and decaySlope went to a non-numeric value on the first set decayDurationSec is 0 Set a real duration, then hold reset true for a cycle to clear it
busy went true and never came back down A negative decayStartTimeSec or decayDurationSec Write non-negative values, then pulse reset
output climbed higher and higher Expected: set adds to what is already there, and the events are firing faster than they finish Lengthen the gap between events, or bound output downstream
The hold restarted part way through a ramp Expected: a set during an event restarts the hold from the new height Gate your event source on busy being false
output will not stay where I write it Expected: the block drives output to defaultValue every idle cycle Use the block’s own input and set; nothing else can hold this path
Nothing happened when I changed input Expected: input is only read at the moment of a set Pulse set after setting the amount
The block fires continuously set is driven from a link that writes it every cycle Drive set from a one-shot source; the block clears it itself
The block is stuck at defaultValue and ignores set reset is being held true reset is a level, not a pulse — write it false
reset stopped the event with a jump rather than a fade Expected: a reset is a step to defaultValue Let the ramp finish, or use a longer duration and reset later
The ramp finished early and snapped the last part decayDurationSec was shortened while the event was running Change the durations only while busy is false
output went below defaultValue and then came back up decayDurationSec was lengthened while the event was running Change the durations only while busy is false
One channel decayed and the others did not Not possible — all channels share one schedule Check the other channels' input values; a zero amount gives a zero jump
Channels finished at different levels Expected: each channel returns to its own defaultValue Set every element of defaultValue
The fall is curved, not straight Not possible — the ramp is always linear Nothing to change; this block has no exponential mode

A starting point for a 0.5-unit nudge that holds briefly and fades over half a second:

defaultValue      = 0.0
decayStartTimeSec = 0.1
decayDurationSec  = 0.5

Limits and errors

Limit Set by What happens Reported
decayDurationSec > one task period Nothing Not checked. A value of 0 makes output and decaySlope non-numeric on the next set, and only reset clears it. Always set a real duration Not reported
decayStartTimeSec ≥ 0 Nothing Not checked. A negative value makes the event never finish and busy stay true forever. Clear it with reset Not reported
output Nothing Unbounded, and it accumulates across overlapping events. Bound it downstream if the consumer needs a limit Not reported
Event schedule Shared One schedule for all channels. A set starts every channel, and there is no per-channel event Not reported
Durations after a task-rate change Task rate Both are in seconds and keep their meaning, so nothing needs rescaling Not reported
Channel count Machine configuration Fixed once the controller starts; it cannot be changed at runtime Not reported

The block raises no errors or warnings and logs nothing. Every failure above shows as a value on a trace, not as a message.


Verified against motorcortex-control3 3.30.0 (bc348fd).