● MAPPING RUNNER & MODES REFERENCE

Mapping Runner & Modes

How DriverMappingRunner orchestrates bindings, what each BindingMode does to a signal, and how to combine them into recipes that drive lights, sounds, blendshapes, or anything else you can wire to a channel.
1
Orientation — what is the Runner?
DriverMappingRunner.cs · Mapping/

The DriverMappingRunner is the component that connects your DataBus channels (the live numbers flowing in from MediaPipe, audio analysis, time, manual sliders, etc.) to your scene objects (transforms, blendshapes, lights, materials, audio sources, animator parameters, ParticleSystems, even arbitrary reflected fields).

It does this through DriverBindings — small data records that say "read this channel, transform it, then push it to that target". The Runner holds a list of these and re-applies them every frame (in LateUpdate, after gameplay/animation have settled).

i
One Runner per scene is typical. Drop the component on any GameObject, fill in its lists, and it'll keep the scene in sync with whatever the DataBus is producing. Multiple Runners is allowed if you want logical separation (e.g. "audio bindings" vs "visual bindings").

Architecture at a glance

DataBus channels ──► DriverMappingRunner ──► scene objects │ ├─ sceneBindings (DriverBinding list, persists with scene) ├─ assetMapping (DriverMapping ScriptableObject, shareable) ├─ mappingLibrary[] (extra DriverMapping assets for runtime swap) └─ assetTargets[] (scene refs wired into the asset's binding slots)
2
Three ways to use the Runner
Scene bindings · Asset mappings · Library swap

Three operating modes coexist on the same Runner. You can use one, two, or all three at once — they're processed in this order each frame: scene bindings first, asset mapping second. If they push the same target, the asset mapping wins (it runs last).

2.1 · Scene Bindings RECOMMENDED FOR MOST USES

Direct DriverBinding entries living on the Runner itself (sceneBindings list). Scene refs go straight into each binding's targetObject / targetComponent fields. Everything saves with the scene file — open the scene tomorrow, hit Play, it just works.

Best for: one-off scenes, learning, iterative tweaking, anything where you don't need to share the mapping with another scene.

2.2 · Asset Mapping SHAREABLE

A DriverMapping ScriptableObject (a .asset file) holds a list of bindings. The Runner references one asset via assetMapping. Because the asset has no scene-specific references, it can be re-used in any scene or shared between projects.

Scene-specific wiring lives separately in assetTargets[] on the Runner — an array of BindingTarget structs that supply the actual scene objects each binding in the asset should drive. The asset is the recipe; the targets are the ingredients.

Best for: rigs you want to clone across multiple scenes ("the avatar mocap rig"), version-controlled mapping libraries, projects with multiple characters using the same mapping logic.

2.3 · Mapping Library + Runtime Swap PERFORMANCE-READY

mappingLibrary[] is a list of multiple DriverMapping assets. The Runner cycles through them at runtime, driven by a single DataBus channel value via the swapKeyPath field (default "m").

When the channel's value rounds to 0, asset index 0 plays. When it rounds to 1, asset index 1 plays. And so on. This lets a performer flip between completely different mappings mid-show — same body, totally different effects routing.

!
Important constraint: assetTargets[] is shared across all assets in the library. So every asset in the library must use the same number of bindings in the same order, with the same expected target types. Plan the slot ordering up front.

Runner inspector at a glance

FieldPurpose
sceneBindingsList of DriverBindings with direct scene refs — saves with scene.
assetMappingOne DriverMapping ScriptableObject. Optional.
assetTargetsPer-binding scene-object wiring used by both assetMapping and mappingLibrary[].
mappingLibraryMultiple DriverMappings cycled by swapKeyPath's channel value.
swapKeyPathChannel that picks the active library index. Default "m".
runInLateUpdateIf true (default) bindings apply after animation. If false, in Update.
3
The per-frame pipeline
What happens to one channel value, every frame

Every binding pushes one channel value through this six-step chain, then applies the result to its target. Understanding the order matters — most "why doesn't this respond the way I expect" issues come from an earlier stage clamping or smoothing the value out before later stages can react.

1. Read channel 2. Remap + clamp + invert 3. Response curve 4. Apply mode 5. Smoothing 6. ApplyToTarget

① Read channel

DataBus.ReadFloat(channelKey). Returns 0 if the key doesn't exist — bindings to mistyped keys silently produce 0 rather than crashing.

② Remap + clamp + invert

inputMin..inputMax → outputMin..outputMax. If clamp is on, values outside inputMin..inputMax get clipped to the output range. invert swaps high/low.

③ Response curve

An AnimationCurve evaluated on the normalised 0..1 value before final scaling. Use it to shape ease-in / ease-out / threshold cliffs.

④ Apply mode

Replaces the value based on logic — passthrough, binary threshold, gated, latched, sequence step, or pulse. Detailed in section 4.

⑤ Smoothing

Lerp low-pass towards the target value. 0 = no smoothing (snap), 0.99 = heavy smoothing (slow). Frame-rate dependent — tune for your average fps.

⑥ ApplyToTarget

Big switch on targetType — writes the final value to the correct property (transform.position.x, BlendShape weight, AudioSource.volume, MaterialColor hue, etc).

Mental model: "Map the channel into the range I want (step 2), shape its response (step 3), turn it into the right kind of signal (step 4), smooth out the jitter (step 5), apply (step 6)."
4
BindingMode reference
Six modes · what each does · when to use it · worked recipes

The BindingMode field on every DriverBinding decides what the binding does with its remapped value. Get this right and the whole pipeline falls into place; pick the wrong mode and you'll fight the signal forever.

!
Rising-edge gotcha (Switch · Latch · Sequence · Pulse): These modes trigger on the moment the channel crosses modeThreshold from below. If a channel starts above the threshold, the first trigger won't fire until the channel dips below and rises again. Pre-process with a ThresholdProcessor or arrange the binding so the channel starts at 0.

● Direct

passthrough · continuous

What it does

The remapped value passes straight through to the target. No logic, no thresholds — just scaled channeltarget property.

channel ──► remap ──► curve ──► value ──► target

When to use

For anything continuous: position, scale, rotation, volume, blendshape weight, light intensity, material colour hue, animator float parameters. The default and most common mode by far.

Recipes

VolumeRight wrist height → music volume. Channel: pose/landmark/rightWrist/y. Remap -0.3..0.7 → 0..1. Target: AudioVolume on your main music source. Wrist drops below mid-chest → silence; arms-overhead → full volume.
GlowForward body lean → emissive intensity. Channel: pose/body/centre/z. Remap to 0..3 with a slight ease-in curve. Target: MaterialFloat _EmissionStrength on a cube. Smoothing 0.4 so the glow doesn't pulse with every breath.
MouthJaw open → blendshape. Channel: pose/joint/jaw/openNorm. Remap 0..0.4 → 0..100 (blendshape weights are 0–100). Target: BlendShape on a face mesh. Smoothing 0.1 — too much and the mouth lags behind speech.

● Switch

binary · threshold-triggered

What it does

Binary on/off based on whether the channel value crosses modeThreshold. Above → outputMax. Below → outputMin. The value between those is never visited — Switch produces a hard step.

above threshold ──► outputMax channel ──► remap ──► below threshold ──► outputMin

When to use

For state-flips: play/stop, on/off, enabled/disabled. Light switches, audio play-stop, animator booleans, mute toggles, "is the user posing yes-or-no?".

Recipes

AudioLifted right hand → loop starts. Channel: pose/landmark/rightWrist/y. modeThreshold 0.4. outputMin=0, outputMax=1. Target: AudioPlayStop on a snare loop. Hand drops → stops. (This is exactly what your existing snare binding does.)
LightCrossed arms → red alarm. Channel: a derived "arms-crossed" signal from a CombineProcessor. Switch with threshold 0.5 turning a LightColor binding from outputMin (white) to outputMax (red). Effective at moments of dramatic intention.
UISmile → enable bloom. Channel: a face-blendshape-derived smile signal. Switch → MaterialFloat on a postprocess proxy. Threshold 0.6 keeps casual expressions from triggering.

● Gate

conditional passthrough · two channels

What it does

The binding's main channel passes through only when a separate gateChannel reads above 0.5. When the gate is closed, output snaps to outputMin. When open, normal Direct-mode behaviour.

gateChannel > 0.5 ──► pass channel value ──► remap ──► target gateChannel ≤ 0.5 ──► outputMin ──► target

When to use

When you want one channel to arm another. Examples: "only let the right hand drive the synth pitch while the left hand is raised"; "only respond to facial blendshapes while the user is looking at the camera".

Recipes

PerformanceLeft-hand chord while right-hand pitches the lead. Right-wrist X drives PaulXTrackProperty.PitchShift in Direct mode. Use Gate so the pitch only modulates while left-hand height clears a threshold — i.e. you only "play" when the gesture is committed.
FocusEyes-on-camera enables animations. Gate channel: head-orientation-Z dot product with camera-forward (a derived channel from a CombineProcessor). Animator Float bindings that depend on look-direction get gated by this — body movement stops driving them when the performer turns away.
!
Gate tests the gateChannel's raw DataBus value > 0.5 — the binding's own remap doesn't affect the gate check. If your gate channel doesn't cleanly straddle 0.5, run it through a ThresholdProcessor first.

● Latch

rising-edge capture + hold

What it does

On the rising edge (channel crosses modeThreshold from below), captures the current value and holds it indefinitely — even if the channel changes. Only releases when a separate resetChannel reads above 0.5.

channel ──► /\_______ ────► resetChannel pulse │ ────► return to live tracking capture + hold value

When to use

For snapshot behaviour. "When I clap, freeze the current pitch and hold it"; "When I tilt my head, remember that head angle until I reset". Anything where you want one moment captured and preserved.

Recipes

CaptureClap-to-freeze the loop's pitch. Latch on right-wrist-Y to PaulXTrackProperty.PitchShift. resetChannel: pose/joint/leftElbow/raiseNorm. Move your wrist to pick a pitch, clap to freeze, raise the other elbow to release.
PoseSnap-to-pose blendshape. Latch on a face-shape channel into a BlendShape target. Hold any expression for >0.5 s to "lock" it; reset via a wink-channel.

● Sequence

rising-edge step · cycles values

What it does

Steps through the sequenceValues[] array, advancing one index every rising edge. Output is the value at the current index. Wraps to the beginning when it runs out.

edge ① → outputs sequenceValues[0] edge ② → outputs sequenceValues[1] edge ③ → outputs sequenceValues[2] edge ④ → wraps, outputs sequenceValues[0]

When to use

For discrete state-cycling. Lighting cue changes (red → blue → green → black), audio loop swaps (intro → verse → chorus → outro), animator-state triggers (idle → guard → strike).

Recipes

LightsStomp foot → cycle lighting state. Channel: pose/landmark/rightAnkle/y with modeThreshold 0.2 (a foot-stomp briefly drops then rises). sequenceValues: [0, 0.33, 0.66, 1]. Target: AnimatorFloat cueIndex on a lighting director with 4 states.
AudioForward-step → next loop. Channel: body-centre-Z. modeThreshold tuned to require a clear step. Target: an AudioCrossfade between four pre-loaded loops, sequenceValues [0, 1, 2, 3] selecting which.

● Pulse

rising-edge spike · decays over time

What it does

On rising edge, snaps to outputMax. Then decays linearly back to outputMin over pulseDecay seconds — regardless of what the channel does afterwards.

output ──► outputMax ╱╲ ╲╲ ╲╲ (decays over pulseDecay seconds) ╲╲ ╲──► outputMin

When to use

For impact-feel events. Camera shake on punch, screen flash on clap, particle burst on jump, light strobe on snare hit, drum-trigger latency-free playback.

Recipes

ImpactClap → screen flash. Channel: hand-clap distance (derived). Pulse outputMin=0, outputMax=1, pulseDecay 0.25s. Target: MaterialFloat on a fullscreen flash quad's _Alpha. Sharp on the clap, gone before the next.
ParticlesFoot strike → spark burst. Channel: rightAnkle/y with a low modeThreshold. Pulse → ParticleEmission on a sparks system. Decay 0.1s for a clean one-shot.
ShakeBig-gesture punch → camera shake. Channel: a velocity-derived signal. Pulse outputMax=1 → MaterialFloat fed into a shader-based camera-shake amount, decay 0.4s.
Pulse is the secret weapon for "feels alive" performance work. Pair it with a continuous Direct binding on the same target — the Direct holds the resting state, the Pulse adds spikes on top. Use ParticleSystems and Pulse-driven emission for the cleanest "trail of action behind the gesture".
5
BindingTargetType cheat sheet
28 target types · what each writes to

The targetType field decides which kind of property the binding pushes its final value into. Grouped here by category.

Group · Target TypeWhat it writes to
Transform
TransformtargetTransform.position / rotation / scale on one chosen axis
Mesh + Animator
BlendShapeSkinnedMeshRenderer blendshape weight 0–100 (auto-remapped from 0–1)
AnimatorFloatAnimator float parameter
AnimatorBoolAnimator bool parameter (true if value > 0.5)
AnimatorTriggerAnimator trigger — fired on rising edge only
Material
MaterialFloatNamed shader float on any material (e.g. _Roughness)
MaterialColorInterprets value as hue, applies via HSVToRGB. Falls back to _BaseColor_Color if the named property doesn't exist.
Light
LightIntensityLight.intensity
LightRangeLight.range
LightColorLight.color — interpreted as hue, like MaterialColor
Particles
ParticleEmissionParticleSystem.emission.rateOverTime
Audio (built-in)
AudioVolumeAudioSource.volume
AudioPitchAudioSource.pitch
AudioCrossfadeMulti-source crossfade index
AudioPlayStopPlay above threshold, Stop below
AudioSpatialX / Y / Z3D AudioSource world position component
AudioReverbZoneAudioReverbZone parameters
Audio (PaulXStretch — OSC to Reaper)
PaulXTrackPropertyAny of the 9 PaulXStretch params (StretchAmount, PitchShift, FreqShift, FilterLow / High, Spread, Compress, MainVolume, Freeze) on a PaulXTrack component
Audio (UnityAudioFX — in-process)
UnityAudioFXPropertyAuto-discovered FX on an AudioSource: AudioLowPassFilter, HighPassFilter, Echo, Distortion, Chorus, Reverb
Escape hatch
ReflectionArbitrary float/bool/int/double member by name on any component. Component lookup is case-insensitive Contains-match on short or full type name.
i
Reflection is the escape hatch for anything not in the explicit list. If you can name the component and the field/property, Reflection can write to it. Best used when no dedicated target type exists for what you need.
6
Workflow recipes
Common patterns assembled from the modes above

R1 · Continuous + impact (Direct + Pulse on same target)

Two bindings on the same MaterialFloat _Glow. First is Direct on body-centre-Z giving a resting glow (0..0.3). Second is Pulse on a clap-channel adding an outputMax=1 spike with 0.2s decay. The Direct holds the baseline, the Pulse adds drama. Listing order matters — the Pulse should come after the Direct so its result wins per frame.

R2 · Latch a setting, control with another

A Latch binding captures the current head-pitch and holds it as a cameraTilt animator float. A second Direct binding on shoulder-Z applies live tweaks on top. The Latch establishes the base, the Direct nudges it. Reset the latch via clap or any chosen reset channel.

R3 · Sequence cycles loops, Direct trims volume

Sequence binding on a foot-stomp channel cycles AudioCrossfade across 4 loops. A separate Direct binding on right-wrist-Y controls the master volume of the cross-faded output. Performer can change track and adjust loudness independently with each hand.

R4 · Gate two performers

Two body sources (rare but possible with two cameras + namespaced channels) feed the same scene. Use Gate to enable Performer A's bindings only when Performer A is on the left half of the frame, and B's bindings only when B is on the right. Channel: pose/body/centre/x with appropriate ranges as the gate channels.

R5 · Pulse spawns particles along motion

One ChannelTrail (3D, scene-level — coming in a future stage) plus a Pulse binding on each major joint's velocity → ParticleEmission. The trail records the path; pulses spawn bursts of particles at moments of high velocity along it. Different particle systems per joint give per-limb "trails of action".

R6 · Library swap for sets

Three DriverMappings in mappingLibrary[], one per "set" of a performance. swapKeyPath bound to a manual ChannelSliderPanel slider — between sets the operator nudges the slider to advance. Same body, fundamentally different effect routing.

7
Troubleshooting
Decision tree for the common "why isn't this working?" cases

Nothing is happening at all

Open the F12 DataFlowMonitor panel and confirm the channel actually has a non-zero value. If it's flatlined, the source isn't writing — check the MediaPipePoseRunner is active, the camera frame is updating in the F10 preview, and the channel key spelling matches exactly (channel keys are case-sensitive).

→ if channel has values but binding stays at 0: check the binding's input range. A channel reading 0.2 with inputMin/Max of 0.5..1 maps to outputMin (your "off" value).

Value moves but in the wrong range

The remap is off. Open F11 ChannelSliderPanel, override the channel value, and watch what the target does. Then adjust inputMin/Max + outputMin/Max accordingly.

→ for blendshapes specifically, remember weights are 0–100 not 0–1. The editor auto-remaps but only if you haven't manually changed outputMax.

Switch / Latch / Sequence / Pulse doesn't fire the first time

The rising-edge gotcha. Rising-edge modes need to see the signal cross modeThreshold from below. If the channel starts above the threshold, the first trigger is "swallowed" until the value dips and rises again.

→ fix by adding a ThresholdProcessor on the source channel that emits a clean 0 when below threshold, or by arranging the binding's input range so it starts below the threshold on scene-load.

Gate doesn't open

The gateChannel test is a literal > 0.5 on the raw DataBus value — your binding's remap doesn't affect it.

→ inspect the gate channel directly in F12 DataFlowMonitor. If it never crosses 0.5, either pick a different channel or pre-process with ThresholdProcessor / SmoothProcessor to produce a clean 0–1 signal.

Values are jittery / shaky

Increase smoothing on the binding (0.3–0.7 is a good first range). For very noisy channels (face landmarks especially), upstream SmoothProcessor in the Processors chain helps too — pre-smoothing the channel makes every binding feel calmer.

→ trade-off: higher smoothing = slower response. Tune visually with the F11 panel by sweeping the channel and watching the lag.

Pulse fires twice in quick succession

The channel is bouncing across the threshold — common with noisy face/hand channels. Increase the channel's upstream SmoothProcessor amount, raise modeThreshold above the noise floor, or add a Switch in front to debounce.

Color / hue feels off

MaterialColor and LightColor interpret the binding's output value as a hue, not as a single RGB channel. Output ranges that span 0..1 sweep the full rainbow. If you wanted a fixed colour at variable intensity, use a MaterialFloat / LightIntensity binding instead and pre-set the colour manually.

Asset mapping plays but Library swap doesn't

Two things to check: (1) the swapKeyPath channel is actually being written — confirm in F12. (2) All assets in mappingLibrary[] use the same binding count + target order, since assetTargets[] is shared across them.

Play-mode tweaks vanish on exit

Standard Unity behaviour — anything you change in Play mode resets when Play stops. Two workarounds:

① Make changes when not in Play (the inspector accepts edits while the runner sleeps).

② In Play, right-click the changed component header → Copy Component, exit Play, right-click → Paste Component Values.

Three diagnostic panels you should know cold: F10 (webcam preview, with the new grid overlay), F11 (manually push channels), F12 (read-only live values). Most problems are diagnosed with one of these three before you ever touch a script.