> This page is for version 1.4.0 (Latest) (default).
> For other versions, use one of these documentation indexes:
> - 1.4.0 (Latest) (default): https://docs.nvidia.com/halos/outside-in-safety/1.4.0/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/halos/outside-in-safety/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/halos/outside-in-safety/_mcp/server.

# Event Mapping Configuration

> How the MDX client turns VSS Kafka messages into Safety Core events: file format, rule matching, validation, and the mappings shipped for each application.

The MDX client, `mdx_client`, consumes the VSS Kafka topics `mdx-events` and `mdx-frames` and
turns matching messages into Safety Core events. The event mapping, passed with `--config`, decides
which messages match and which event type (`EVENT_0` to `EVENT_23`) each one becomes. The SDM
configuration then decides what each event type means to a decision maker: the two must use the
same codes.

## File format

The mapping is a Protobuf `EventMappingConfig` message: a list of `rules { ... }` blocks. The MDX
client reads the file as binary Protobuf first and, if that fails, as Protobuf text format. A field
name it does not know makes the file fail to load.

```protobuf
rules {
  name: "Person restricted area ROI violation"
  message_source: "mdx-frames"
  alert_type: "roi"
  object_type: "person"
  rule_id: "roi-id-1"
  restricted_area_violation: "true"
  output_event: "EVENT_4"
}
```

### Rule fields

| Field                                                                                              | Effect                                                                                                                                                                                                                                                                |
| -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                                                                                             | Label used in logs and in `--debug` output.                                                                                                                                                                                                                           |
| `message_source`                                                                                   | `mdx-events` or `mdx-frames`.                                                                                                                                                                                                                                         |
| `alert_type`                                                                                       | The kind of alert. From `mdx-events` the MDX client produces `tripwire` and `roi`. From `mdx-frames` it produces `roi`, `social_distancing`, `object_presence` and `co_movement`. `empty_roi` is accepted in a rule but never produced, so such a rule never matches. |
| `event_type`                                                                                       | Direction or transition, for example `IN`, `OUT`, `ENTRY`, `EXIT`.                                                                                                                                                                                                    |
| `object_type`                                                                                      | Object class, for example `person` or `forklift`.                                                                                                                                                                                                                     |
| `rule_id`                                                                                          | The tripwire or ROI identifier, or, for a proximity-pair rule, the rule's own unique identifier.                                                                                                                                                                      |
| `restricted_area_violation`, `confined_area_violation`, `social_distancing_violation`              | Violation filters for `mdx-frames`. See [Violation filters](#violation-filters).                                                                                                                                                                                      |
| `object_type_primary`, `object_type_secondary`, `proximity_violation`, `distance_threshold_meters` | Proximity-pair fields. See [Proximity-pair rules](#proximity-pair-rules).                                                                                                                                                                                             |
| `scale_factor`                                                                                     | `mdx-frames` rules only: report every Nth frame per sensor. A value of 1 or less, or no value, reports every frame.                                                                                                                                                   |
| `output_event`                                                                                     | The event type to report: `EVENT_0` to `EVENT_23`.                                                                                                                                                                                                                    |

`severity` is reserved: a rule that contains it fails to load. `rule_id_prefix_match` is still in
the message but is ignored. Co-movement rules (`alert_type: "co_movement"` with the
`comovement_*` fields) are accepted, but no shipped mapping uses them.

## Matching

* Rules are tried in file order, and the **first** matching rule is used. If that rule's
  `scale_factor` skips the current frame, the message is dropped: it does not fall through to
  later rules.
* An empty field matches any value. Every other string field must match exactly, ignoring case.
* `rule_id` also matches a producer identifier of the form `<rule_id>:<suffix>`. In that form the
  `<rule_id>` part is compared with case.
* The restricted-area "cleared" events that the MDX client creates itself (see below) compare ROI
  identifiers with case. Keep `rule_id` in exactly the case VSS uses.
* The sensor name in the message is then looked up in the identity file to find its `pipelineId`:
  `sensor_config.conf` in 2D, `sensor_pipelines_config.conf` in 3D. A message whose sensor is not
  there is dropped with `Dropping event: sensor not in sensor_config, so it has no pipelineID: <name>`.
  See [Sensor files](/halos/outside-in-safety/reference/sensor-files).

Use `mdx_client --debug` to see which rule each message matches. In that mode the MDX client prints
`[MDX_DEBUG] Matched rule: ...` instead of reporting events, and does not register with the SEI
daemon.

## Violation filters

| Filter                        | Values                                               | Requires                                                          |
| ----------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------- |
| `restricted_area_violation`   | `"true"`, `"false"`, `"any"` or empty, in lower case | `message_source: "mdx-frames"`, `alert_type: "roi"`               |
| `confined_area_violation`     | The same                                             | `message_source: "mdx-frames"`, `alert_type: "roi"`               |
| `social_distancing_violation` | The same                                             | `message_source: "mdx-frames"`, `alert_type: "social_distancing"` |

* A rule may set at most one filter.
* `"any"` is not the same as empty: it still restricts the rule to that kind of candidate.
* For a rule with `restricted_area_violation: "true"`, the MDX client also creates a "cleared"
  candidate when the area is present in a frame but no violator is in it. A matching rule with
  `restricted_area_violation: "false"` turns that into the cleared event, as `EVENT_5` does in the
  forklift mapping.

## Proximity-pair rules

A proximity-pair rule reports the distance between two object classes in `mdx-frames`:

```protobuf
rules {
  name: "Forklift-Person 2m >= distance > 1m"
  message_source: "mdx-frames"
  alert_type: "social_distancing"
  rule_id: "forklift_person_lt_2m"
  object_type_primary: "Forklift"
  object_type_secondary: "Person"
  proximity_violation: true
  distance_threshold_meters: 2.0
  output_event: "EVENT_13"
}
```

A rule is a pair rule when it sets `proximity_violation`, `distance_threshold_meters`,
`object_type_primary` or `object_type_secondary`. It must then have:

* `message_source: "mdx-frames"`, `alert_type: "social_distancing"`, a primary type, and
  `proximity_violation` present, with no violation filter;
* for `proximity_violation: true`, a finite `distance_threshold_meters` greater than 0; for
  `false`, no threshold;
* a `rule_id` that is not empty, shorter than 64 characters, and unique (ignoring case);
* at most one rule per pair and threshold, and at most one "no violation" rule per pair.

The two object types are matched in either order. The distance is the 3D distance between the two
objects. The rule with the smallest threshold that is at least the distance wins. The "no
violation" rule applies when the frame reports no social-distancing violation.

When a frame reports a violation, a pair whose largest `distance_threshold_meters` is above the
social-distancing threshold reported in that frame is skipped for the frame. Configure the VSS
social-distancing threshold to at least the largest threshold in your pair rules.

For binary Protobuf mappings, keep `proximity_violation` and `distance_threshold_meters` as present
or absent exactly as above: their presence is part of the rule.

## Validation and errors

`mdx_client` validates the mapping at startup and exits if it is unusable:

| Problem                                           | Message                                                          |
| ------------------------------------------------- | ---------------------------------------------------------------- |
| No `--config`                                     | `error: config path is required (use -c or positional argument)` |
| File missing or not Protobuf                      | `Failed to load config file: <path>`                             |
| No rules                                          | `No rules in config or load failed`                              |
| A rule that cannot be read                        | `Invalid event mapping rule`                                     |
| A rule that breaks the filter or pair rules above | `Rejected unsafe event mapping rule`                             |

`message_source`, `alert_type` and `output_event` are not checked against known values. A typo
loads cleanly and the rule never matches. An unknown `output_event` makes matching messages report
`EVENT_UNKNOWN`, which no SDM can subscribe to. Check new rules with `--debug`.

Restart the MDX client, or the Safety Core, after changing a mapping.

## Shipped mappings

| File                                                                    | Used by default for `--app`                 | Rules                                                                                       |
| ----------------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `/opt/nvidia/psf/apps/atl/event_mapping_atl.pb.txt`                     | `atl`, `atl,atl`, `atl_dual`                | Forklift safety function, zone 1 (`EVENT_0`–`EVENT_5`) and zone 2 (`EVENT_6`–`EVENT_11`)    |
| `/opt/nvidia/psf/apps/proximity/proximity_event_mapping.pb.txt`         | `pxc`, `pxc,pxc`, `atl,pxc`, `pxc,atl`      | Both forklift zones (`EVENT_0`–`EVENT_11`) and four proximity pairs (`EVENT_12`–`EVENT_23`) |
| `/opt/nvidia/psf/apps/atl_proximity/event_mapping_atl_proximity.pb.txt` | `atl_proximity` (container entrypoint only) | Forklift zone 1 (`EVENT_0`–`EVENT_5`) and one forklift–person pair (`EVENT_8`–`EVENT_10`)   |

The launchers pass `--event-mapping <file>` through to the MDX client in place of the default; see
[Launchers and command-line options](/halos/outside-in-safety/reference/launcher). With `launch_hoisa.sh --runtime docker`
the MDX client runs in the container and reads the image's copy of the default mapping, so editing
the host's copy has no effect; pass your file with `--event-mapping` instead.

### Forklift safety function

Zone 1, as shipped:

```protobuf
rules {
  name: "Forklift tripwire OUT"
  message_source: "mdx-events"
  alert_type: "tripwire"
  event_type: "OUT"
  object_type: "forklift"
  rule_id: "tripwire-id-1"
  output_event: "EVENT_0"
}
rules {
  name: "Forklift tripwire IN"
  message_source: "mdx-events"
  alert_type: "tripwire"
  event_type: "IN"
  object_type: "forklift"
  rule_id: "tripwire-id-1"
  output_event: "EVENT_1"
}
rules {
  name: "Person tripwire OUT"
  message_source: "mdx-events"
  alert_type: "tripwire"
  event_type: "OUT"
  object_type: "person"
  rule_id: "tripwire-id-1"
  output_event: "EVENT_2"
}
rules {
  name: "Person tripwire IN"
  message_source: "mdx-events"
  alert_type: "tripwire"
  event_type: "IN"
  object_type: "person"
  rule_id: "tripwire-id-1"
  output_event: "EVENT_3"
}
rules {
  name: "Person restricted area ROI violation"
  message_source: "mdx-frames"
  alert_type: "roi"
  object_type: "person"
  rule_id: "roi-id-1"
  restricted_area_violation: "true"
  output_event: "EVENT_4"
}
rules {
  name: "Person restricted area ROI violation cleared"
  message_source: "mdx-frames"
  alert_type: "roi"
  object_type: "person"
  rule_id: "roi-id-1"
  restricted_area_violation: "false"
  output_event: "EVENT_5"
}
```

| Input                             | Zone 1    | Zone 2     | Meaning to `atl_sdm` (`--config` key)             |
| --------------------------------- | --------- | ---------- | ------------------------------------------------- |
| Forklift crosses the tripwire OUT | `EVENT_0` | `EVENT_6`  | Forklift entered the trailer (`forklift_entered`) |
| Forklift crosses the tripwire IN  | `EVENT_1` | `EVENT_7`  | Forklift left the trailer (`forklift_exited`)     |
| Person crosses the tripwire OUT   | `EVENT_2` | `EVENT_8`  | Person entered the trailer (`person_entered`)     |
| Person crosses the tripwire IN    | `EVENT_3` | `EVENT_9`  | Person left the trailer (`person_exited`)         |
| Person in the restricted area     | `EVENT_4` | `EVENT_10` | Restricted-area violation (`roi_violation`)       |
| Restricted-area violation cleared | `EVENT_5` | `EVENT_11` | Violation cleared (`roi_cleared`)                 |

Zone 1 uses `tripwire-id-1` and `roi-id-1`; zone 2 uses `tripwire-id-2` and `roi-id-2`, with rule
names prefixed `Zone2`. The `atl_sdm_zone1.conf.sample` and `atl_sdm_zone2.conf.sample` templates
bind these codes.

### Proximity Monitoring

`proximity_event_mapping.pb.txt` holds both forklift zones above, and these pair rules. Each is
`mdx-frames` / `social_distancing` with `Person` as the secondary type:

| Primary type              | `rule_id` prefix           | No violation | 2 m or less | 1 m or less |
| ------------------------- | -------------------------- | ------------ | ----------- | ----------- |
| `Forklift`                | `forklift_person_`         | `EVENT_12`   | `EVENT_13`  | `EVENT_14`  |
| `Agility_Digit_Humanoid`  | `agility_digit_person_`    | `EVENT_15`   | `EVENT_16`  | `EVENT_17`  |
| `Transporter`             | `transporter_person_`      | `EVENT_18`   | `EVENT_19`  | `EVENT_20`  |
| `Fourier_GR1_T2_Humanoid` | `fourier_humanoid_person_` | `EVENT_21`   | `EVENT_22`  | `EVENT_23`  |

The `rule_id` suffixes are `no_violation`, `lt_2m` and `lt_1m`. `proximity_sdm` maps the three
codes of each pair to `<group>_normal`, `<group>_reduce` and `<group>_stop` in its `--config`
file. The `pxc_sdm_forklift.conf.sample` template selects the forklift pair; the
`pxc_sdm_robots.conf.sample` template selects the other three.

### Forklift safety function with proximity

`event_mapping_atl_proximity.pb.txt` holds forklift zone 1 (`EVENT_0`–`EVENT_5`) and three pair
rules with primary `forklift` and secondary `person`: `forklift_person_no_violation` (`EVENT_8`),
`forklift_person_lt_2m` (2.0 m, `EVENT_9`) and `forklift_person_lt_1m` (1.0 m, `EVENT_10`).
`EVENT_6` and `EVENT_7` are unused. `atl_sdm` receives `EVENT_0`–`EVENT_5` and
`atl_proximity_sdm` receives `EVENT_8`–`EVENT_10`.

## Before you change a mapping

1. Confirm that the sensor names VSS emits are in the identity file
   ([`sensor_config.conf`](/halos/outside-in-safety/reference/sensor-files#sensor_config) in 2D,
   [`sensor_pipelines_config.conf`](/halos/outside-in-safety/reference/sensor-files#sensor_pipelines_config) in 3D).
2. Confirm that object types and tripwire or ROI identifiers match the deployed VSS configuration,
   including case.
3. Keep the `output_event` codes and the SDM `--config` bindings in step. Two SDMs on one gateway
   must not share a code.
4. Run with `--debug` to see which rule each message matches before you report to the SEI daemon.

Related pages: [SEI Configuration](/halos/outside-in-safety/reference/sei-configuration), and the decision makers in the
[Integration Guide](/halos/outside-in-safety/integration/components/decision-maker).