Interfaces
The cross-gem contract layer — neutral agreements collected in GS_Core so feature gems depend on contracts instead of on each other’s internals. Covers the three contract kinds (TypeBase, Emit, Exchange).
GS_Core/Interfaces/ is the framework’s cross-gem contract layer. Feature gems no longer depend on each other’s internals — they depend on neutral contracts collected here in GS_Core. A contract lives in GS_Core; the feature gem that produces it owns and operates it. GS_Core holds the agreement, the gem holds the behavior.
This is the layer that lets GS_Interaction emit a “pulse received” signal that GS_Juice can react to without either gem including the other.
Contents
Why This Layer Exists
Before this layer, cross-gem features were glued together by bridge components — a component in GS_Complete whose only job was to listen to one gem and drive another (e.g. listen to GS_Unit possession, drive a GS_PhantomCam target). Every such bridge carried a hard dependency on both gems’ internals.
Once both sides speak a Core contract, those bridges collapse into their natural gem and shed the cross-gem-internals dependency. Feature glue now lives in the gem that owns the feature, not in a catch-all integration gem.
The GS_Complete collapse
This effort dissolved GS_Complete’s bridge components: a paper-facing camera handler moved to GS_Performer, and a dialogue-select button, a possession→cam-target utility, and a cinematic controller all repointed to Core contracts and dropped their producing-gem includes.
The Three Contract Kinds
The layer collects exactly three kinds of contract. The kind tells you how to extend it.
| Kind | Pattern | One-line | Extend by |
|---|
| TypeBase | Strategy | “Inherit this and you’re plugged in.” | Subclass a *Type base, reflect it → it appears in the editor’s type-picker. |
| Emit | Observance | “Wait for this signal, then act.” | Handle the *EmissionBus (C++ or ScriptCanvas) and override the event you care about. |
| Exchange | Mediator | “Ask, a provider answers.” | Call the *Exchange query bus. (Rare today — mostly forward-looking.) |
Full mechanics, editor/script appearance, and an authoring how-to for each are on the Classifications page.
The Glance Test
The class/bus suffix encodes scope and kind, so you can classify any bus at a glance:
| Suffix | Meaning |
|---|
*Requests / *Notifications | In-gem bus — internal to one gem, not a cross-gem contract. |
*Type (a base class) | TypeBase contract — a pickable Strategy base. |
*Emissions / *EmissionBus | Emit contract — a one-way observation bus. |
*Exchange / *ExchangeBus | Exchange contract — a two-way request/fulfill bus. |
Never named *Service
A contract bus is never named *Service — that collides with O3DE component-service CRCs (GetProvidedServices). A command/query bus that has not yet earned Exchange status keeps the in-gem *Requests name.
Status at a Glance
| Kind | Shipped | Notes |
|---|
| TypeBase | 9 Strategy bases — built & verified | The compiled, reflected contracts. |
| Emit | 13 observation buses across 8 gems | All built, except GS_Cinematics’ three (Typewriter / Cinematic / DialogueSequence) which are code-complete but pending a build — treat their detail as provisional. |
| Exchange | 1 (CamCoreExchange) | The rest (Play/Stop family, graph parameters, surface query, CamManager queries) are planned and not yet shipped. |
The full per-contract breakdown is in the Contract Reference.
Where Contracts Live
Contracts are collected under GS_Core/Interfaces/<Capability>/ by capability (Camera, Pulse, Trigger, Dialogue, Motion, Targeting, UI, Possession, Cinematics, Time) — not by gem. One capability may be produced by one gem and consumed by several.
- Emit / Exchange buses are header-only in Core (an EBus is runtime, not serialized). The producing gem reflects them to ScriptCanvas.
- TypeBase classes are compiled (
.h + .cpp) and registered in the Core reflection aggregator, so a base is reflected ahead of any gem’s subtypes by module load order.
See the Contract Reference for the full capability-folder map and which gem produces each.
See Also
Get GS_Core
GS_Core — Explore this gem on the product page and add it to your project.
1 - Classifications
The three GS_Core contract kinds in depth — TypeBase (Strategy), Emit (Observance), and Exchange (Mediator) — each with its authoring how-to, plus the naming guard and the hoisting mechanics authors need.
The interfaces layer collects exactly three kinds of cross-gem contract. Each kind has a single, recognisable extension story — the suffix tells you which one you are looking at (see the glance test). This page covers each kind in depth and shows how to author against it.
For the contract concept and why the layer exists, start at the Interfaces overview.
Contents
TypeBase — Strategy
“Inherit this and you’re plugged in.”
A pickable polymorphic base class. An author writes a subclass, reflects it, and it auto-appears in the editor’s type-picker for any component that holds a list of that base. There is no registration call and no manifest — the O3DE SerializeContext drives the picker directly from the reflected inheritance.
TypeBase contracts are the only compiled contracts: they carry reflection (.h + .cpp) and are registered in the Core reflection aggregator so the base is reflected ahead of any gem’s subtypes.
“TypeBase” is the category name we use to talk about them — the classes themselves keep their domain name (PulseType, DialogueCondition, UiMotionTrack, …). That saved name string is the on-disk identity and is frozen: renaming it breaks existing assets.
Examples: PulseType / ReactorType (interaction pulses), WorldTriggerType / TriggerSensorType (world triggers), DialogueCondition / DialoguePerformance / DialogueEffect (dialogue graph nodes), UiMotionTrack / FeedbackMotionTrack (UI & FX motion tracks).
How to: add a new type
- Derive from the Strategy base —
class MyReactor : public GS_Core::ReactorType. - Reflect it to SerializeContext (and EditContext for inspector fields), exactly like any O3DE class.
- Done — it appears in the type-picker dropdown of every component that holds a
vector of that base. No registration call.
Frozen name
The reflected class name string is the saved-data identity. Once shipped it must never be renamed, or assets that reference it fail to load. Rename the C++ identifier freely; keep the reflected name string stable.
Emit — Observance
“Wait for this signal, then act.”
A one-way observation bus. A producer emits a signal; any number of consumers listen. If the producer is not present at runtime, the signal simply never fires — consumers degrade to silence rather than erroring.
Code shape: an EBus interface named *Emissions, aliased *EmissionBus. Each event has an empty default body, so a handler only implements the events it cares about.
Examples: PossessionEmissions (a controller possessed a unit / a unit was possessed), PulseReactorEmissions (a reactor received a pulse), TimeEmissions (world tick, day/night changed), UIPageEmissions (a page shown/hidden). The full list is in the Contract Reference.
Addressing: per-entity vs global
- By EntityId — listen to a specific entity (interaction, UI interactable, motion, possession buses). Connect at the address of the entity you care about.
- Global broadcast — listen for any occurrence of a state change (
TimeEmissions, CinematicEmissions, DialogueSequenceEmissions, UIPageEmissions). Connect to the broadcast.
The possession bus fires controller-vantage events on the controller entity and unit-vantage events on the unit entity.
Fire on resolved state, not on request
Emits fire at the moment the observable state actually changed, not when a command was issued. You can trust their timing:
- “Active camera changed” fires at blend-complete, not when a new camera was requested.
- “Page shown / hidden” fires after the show/hide animation resolves, not on the call.
- “Unit released” carries the released entity (captured before the reference clears), so the payload is meaningful rather than already-nulled.
How to: react to an event
- Handle the bus —
class MyThing : public GS_Core::PulseReactorEmissionBus::Handler (C++), or drop the bus’s event-handler node into a ScriptCanvas graph. - Connect at the right address —
BusConnect(entityId) for per-entity buses, BusConnect() for global broadcasts. - Override only the events you need. Disconnect when done.
ScriptCanvas names are preserved
When a bus was hoisted from a gem into Core, the gem keeps reflecting it to ScriptCanvas
under its original bus name. Existing graphs keep resolving — the C++ type moved, the script-facing name did not. The one exception is
Possession, a deliberate redesign whose name
did change (see
Hoisting Mechanics).
Exchange — Mediator
“Ask, a provider answers.” — mostly forward-looking
A two-way request/fulfill (query) bus: a consumer asks, a single provider answers. An Exchange is created only when a bidirectional cross-gem need is proven — a pull, not a push. Until then a feature’s command surface stays an in-gem *Requests bus.
Code shape: *Exchange / *ExchangeBus.
Shipped today: only CamCoreExchange (GetCamCore — “which entity is the active camera core?”). It pairs with the CamCore lifecycle Emit and data Emit to form an observable-service trio: learn it exists (Emit) → query it (Exchange) → observe its updates (Emit).
Planned (not yet shipped): Play/Stop a sequence / UI motion / feedback emit / soundtrack / audio event; Get/Set a graph parameter; a terrain surface-info query for footstep sounds; CamManager queries. Each planned Play/Stop Exchange will pair with a completion Emit — command via Exchange, observe via Emit.
How to: query a provider
- Call the Exchange bus with a result —
GS_PhantomCam::CamCoreExchangeBus::BroadcastResult(out, &GS_PhantomCam::CamCoreExchange::GetCamCore). - A single provider answers. If no provider is present, the result is the bus’s default.
Verify before authoring
Exchange is the least-built kind. Treat anything beyond CamCoreExchange as a design sketch and confirm against source before writing against it.
Hoisting Mechanics
The defining idea: a contract lives in GS_Core, but the feature gem owns and operates it. Only the neutral C++ interface moves to Core; everything that needs the producer present stays home.
| Piece | Lives in |
|---|
The EBus interface (*Emissions / *Exchange) or the Strategy base (*Type) | GS_Core |
| The emit / broadcast call sites (where the signal fires) | producing gem |
| The ScriptCanvas binder + its BehaviorContext reflection | producing gem |
The command/query half (*Requests) until proven cross-gem | producing gem |
An observation handler is inert without its emitter, so binding the signal into ScriptCanvas only makes sense where the emitter exists — the producing gem reflects it. GS_Core just publishes the shape.
Transparent hoist vs deliberate redesign
- Transparent hoist (same events, new home) — ScriptCanvas name, handler UUID, and translation assets are all preserved. No graph breakage. This is the norm (Time, Interaction, UI, Cinematics emits all did this).
- Deliberate redesign (events split or renamed) — the ScriptCanvas name intentionally changes and old graphs break by design. The one case is Possession: the single “possessed” event (which fired on both possess and depossess) was split into explicit possess + release, across controller and unit vantages. It is now
PossessionEmissionBus.
Possession — graphs need rewriting
Any C++, graph, or example referencing the old UnitControllerNotificationBus / PossessedTargetUnit or UnitNotificationBus::UnitPossessed is describing a bus that no longer carries possession. Rewrite to the four-event PossessionEmissionBus (OnPossessedUnit, OnReleasedUnit, OnPossessedByController, OnReleasedByController). Note the unit standby events stayed in-gem on UnitNotificationBus.
See Also
Get GS_Core
GS_Core — Explore this gem on the product page and add it to your project.
2 - Contract Reference
Every GS_Core contract by capability folder and producing gem — the 9 TypeBase Strategy bases, the 13 Emit observation buses, and the Exchange query buses — with shipped-versus-pending status and a per-gem feature index.
Every contract collected under GS_Core/Interfaces/<Capability>/, the gem that produces it, and its status. For what each kind is and how to author against it, see Classifications.
✦ marks an event or contract created during the interfacing effort (a gap-fill, not a pre-existing notification).
Contents
TypeBase — Strategy Bases
Compiled, editor-pickable. Subclass + reflect → the subtype appears in the type-picker. All nine are shipped and build-verified.
| Contract | Capability folder | Producing gem |
|---|
PulseType | Pulse | GS_Interaction |
ReactorType | Pulse | GS_Interaction |
WorldTriggerType | Trigger | GS_Interaction |
TriggerSensorType | Trigger | GS_Interaction |
DialogueCondition | Dialogue | GS_Cinematics |
DialoguePerformance | Dialogue | GS_Cinematics |
DialogueEffect | Dialogue | GS_Cinematics |
UiMotionTrack | Motion | GS_UI |
FeedbackMotionTrack | Motion | GS_Juice |
DialogueEffect is passive — its reversal logic lives in the dialogue node’s teardown rather than a bus handler. An authoring detail, not a contract change.
Emit — Observance Buses
Header-only. Handle the *EmissionBus and override the events you care about.
| Contract | Events | Capability | Producer | Status |
|---|
CamCoreEmissions | UpdateCameraPosition, OnCamCoreRegistered, OnCamCoreUnregistered | Camera | GS_PhantomCam | ✅ Built |
TimeEmissions | WorldTick, DayNightChanged | Time | GS_Environment | ✅ Built |
PulseReactorEmissions | OnPulseReceived | Pulse | GS_Interaction | ✅ Built |
TriggerSensorEmissions | OnTriggered, OnReset | Trigger | GS_Interaction | ✅ Built |
TargetingEmissions | OnUpdateInteractTarget, OnEnterStandby, OnExitStandby, OnInteract✦ | Targeting | GS_Interaction | ✅ Built |
FeedbackMotionEmissions | OnFeedbackMotionComplete, OnFeedbackMotionLooped | Motion | GS_Juice | ✅ Built |
UIInteractableEmissions | OnClicked | UI | GS_UI | ✅ Built |
UiMotionEmissions | OnUiMotionComplete(entity, name), OnUiMotionLooped(entity, name) | Motion | GS_UI | ✅ Built |
UIPageEmissions | OnPageShown(page, name), OnPageHidden(page, name) | UI | GS_UI | ✅ Built |
PossessionEmissions | OnPossessedUnit✦, OnReleasedUnit✦, OnPossessedByController, OnReleasedByController✦ | Possession | GS_Unit | ✅ Built |
TypewriterEmissions | OnTypeFired, OnTypewriterComplete | Dialogue | GS_Cinematics | ⚠️ Pending build |
CinematicEmissions | EnterCinematic, ExitCinematic | Cinematics | GS_Cinematics | ⚠️ Pending build |
DialogueSequenceEmissions | OnDialogueSequenceStarted✦, OnDialogueTextBegin, OnDialogueSequenceComplete | Dialogue | GS_Cinematics | ⚠️ Pending build |
Cinematics emits are not yet built
TypewriterEmissions, CinematicEmissions, and DialogueSequenceEmissions are code-complete but unbuilt as of this writing. Treat their event lists as provisional and confirm against source before authoring against them.Addressing. CamCoreEmissions (data event) is global. The Interaction / UI-interactable / motion / possession buses are addressed by EntityId — listen to a specific entity. TimeEmissions, CinematicEmissions, DialogueSequenceEmissions, and UIPageEmissions are global broadcasts — listen for any occurrence. The possession bus fires controller-vantage events on the controller entity and unit-vantage events on the unit entity.
Possession replaced two older buses
PossessionEmissions is a deliberate redesign, not a transparent hoist. It replaces the old UnitControllerNotificationBus (PossessedTargetUnit) and UnitNotificationBus::UnitPossessed. The single “possessed” event that fired on both possess and depossess was split into explicit possess/release across controller and unit vantages. Graphs, ScriptCanvas translation assets, and examples using the old names are stale and need rewriting. The unit standby events stayed in-gem on UnitNotificationBus.
Exchange — Mediator Buses
Two-way query buses. Only CamCoreExchange ships today; the rest are planned.
| Contract | Methods | Capability | Producer | Status |
|---|
CamCoreExchange | GetCamCore | Camera | GS_PhantomCam | ✅ Built |
| Play/Stop — Sequence · UIMotion · FeedbackEmit · soundtrack · audio event | — | (multi) | Cinematics / UI / Juice / Audio | ⏳ Planned |
| Get/Set Graph Parameter | — | (graphcanvas graphs) | multi | ⏳ Planned |
| Terrain surface-info query (footstep sounds) | — | Environment | GS_Environment | ⏳ Planned |
| CamManager queries | — | Camera | GS_PhantomCam | ⏳ Planned |
Each planned Play/Stop Exchange pairs with a completion Emit — command via Exchange, observe via Emit. GS_Audio’s emits are greenfield and will land with its Play/Stop Exchange, not before.
Per-Gem Feature Index
What each gem contributed to the layer.
| Gem | Contribution |
|---|
| GS_Core | The Interfaces/ tree, the ReflectAllInterfaces aggregator, and a generic motion-loop hook (OnMotionLoop) added to the shared motion runtime to back the UI & Juice motion-loop emits. |
| GS_PhantomCam | CamCore (Exchange + Emit); CamManager rig-lifetime emits; an always-face-camera helper repointed. The internal “set this camera” command stayed an in-gem bus — it is a manager→core pipe, not a cross-gem contract. |
| GS_Environment | TimeManager day/night and world-tick emits; time control stays in-gem. |
| GS_Interaction | Pulse-reactor, trigger-sensor, and targeting emits; a new OnInteract✦ emit; a dead handler base removed. |
| GS_Juice | Feedback-motion complete/loop emits, wired through the emitter. |
| GS_UI | Button “clicked”, UI-motion complete/loop (with a motion name), page shown/hidden. |
| GS_Unit | Possession (controller & unit vantages, possess & release); in-gem consumers repointed. |
| GS_Cinematics | Typewriter, cinematic enter/exit, dialogue-sequence lifecycle (+ a new sequence-started event). Code-complete, pending a build. |
| GS_Complete | Bridge components collapsed: a paper-facing handler moved to GS_Performer; a dialogue-select button, a possession→cam-target utility, and a cinematic controller all repointed to Core contracts and dropped their producing-gem includes. |
| GS_Performer | Received the paper-facing handler from GS_Complete. |
Deferred / Not Yet in This Layer
- GS_Audio emits — greenfield; ship with the planned Play/Stop audio Exchange.
- CamState hoist — unblocks when the neutral EntityId-addressed Camera Exchange replaces its owner-pointer coupling.
- Possession ScriptCanvas translation assets — regeneration pending.
- A legacy UI hub bus header — stale; cleanup pending (GS_Page is the live page system).
See Also
Get GS_Core
GS_Core — Explore this gem on the product page and add it to your project.