Skip to main content

Events and feedback

Why events only

Events are the only outbound pathway out of the interaction core. Adapters feed input in, through the shared input contracts, and everything downstream, gameplay, UI, feedback, subscribes to events out. There is no side door such as a raw engine tag to listen to instead.

Subscribing

interactions.runtime.onEvent((event) => {
console.log(event.type, event.interactableId, event.value);
});

Each InteractionEvent carries a type and an interactableId, plus optional behaviourKind, interactorId, value and reason fields depending on the event.

EventFires when
hoverEnter / hoverExitAn interactor starts or stops targeting an interactable
pressStart / pressEndA press-capable interactor crosses the press or release threshold
actuated / releasedA press behaviour fires or lets go
valueChangedA behaviour's analog value moves
grabStart / grabEndAn interactor picks up or drops a grabbable interactable
scoredA tossScore behaviour registers a pass through its hoop
dwellProgressA gaze-dwell meter moves
behaviourEnabled / behaviourDisabledCapability negotiation turns a behaviour on or off
interactableEnabled / interactableDisabledsetInteractableEnabled is called

Feedback as a request

The core asks for a haptic pulse or a sound; it never plays either itself. A behaviour emits a FeedbackIntent describing the physical moment, such as "this press just actuated, a short strong pulse would fit", and the app subscribes and realises it however it likes. The cues actually raised today are hover, actuate, release, grab, drop, score and dwellComplete. Two further cues, press and valueTick, exist in the type but are not yet raised by any shipped behaviour.

routeHapticsToProvider

An opt-in helper that forwards the haptic half of every feedback intent to the input provider's pulse. It no-ops on sources with no haptic actuator, such as hands, and only fires when capabilities.haptics is true.

import { routeHapticsToProvider } from "@realitycollective/threejs-interactions";

routeHapticsToProvider(
(listener) => interactions.runtime.onFeedback(listener),
interactions.provider,
);

routeAudioToSink

An opt-in helper that plays a sound for the feedback intents named in a cue map, through whatever the app uses to play audio.

routeAudioToSink(
(listener) => interactions.runtime.onFeedback(listener),
mySink,
{ actuate: "click", score: "chime" },
);

Cues absent from the map are ignored, so an app sonifies only the moments it has sounds for.

Why the core never calls into your code

Events out, never callbacks in. The core has no reference to your scene objects or gameplay state; it only ever emits what happened, so your app decides what to do about it, and a listener can unsubscribe safely from inside its own callback.

More information