---
mvp: yes
subsystem: collision
---

# Collision

GameMaker 5.3a provides three collision primitives that BNO leans on heavily:

- **`collision_rectangle(x1, y1, x2, y2, obj, prec, notme)`** — returns instance ID (or `noone`) of the first instance of `obj` (or any descendant) inside the AABB. `prec=1` triggers per-pixel mask check; `notme=1` excludes the calling instance.
- **`collision_line(x1, y1, x2, y2, obj, prec, notme)`** — same shape, line segment. Used for line-of-sight and corner detection.
- **Per-object Collision-N events** — fire automatically when this instance's mask intersects an instance of object N. The engine does the AABB+mask test every step and dispatches.

BNO uses all three. The player Step event in `extracted/client-5-8/objects/0042-player/events/Step.gml` shows the collision_rectangle idiom:

```gml
//Depth
if(sprite_index != Hexport && sprite_index != HexportIn && sprite_index != HexportOut)
{
depth_set(0,43);
if(mask_index != NaviMask && sprite_index != HexportOut)
  mask_index = NaviMask;
}
else if(sprite_index == Hexport)
{
if(collision_rectangle(x-8,y-8,x+sprite_width+8,y+sprite_width+8,hxtsideb,0,1))
{
  if(dlayer != 4) depth_set(4,23);
}
else if(dlayer != 6) depth_set(6,23);
if(mask_index != HexportMask) mask_index = HexportMask;
}
```

Two things stand out:

1. **Mask swapping with state.** When the sprite changes (`Hexport` vs `HexportMask`), the player's `mask_index` is rewritten. Hexport is the projected-shadow sprite; HexportMask is its narrower hit volume. This is BNO's idiom for "use a smaller hitbox while transitioning between layers."
2. **AABB pad (+8 / -8).** The collision test queries an 8-pixel-larger box than the sprite, looking for `hxtsideb` (a hexport-side beacon object). When the player is near a hexport edge, the depth swaps from 6 to 4 to render in front of the foreground tile.

The terrain-edge tile-corner scripts (`0337 cornerul`, `0338 cornerl`, `0339 cornerr`, `0340 corneru`, `0341 cornerd`, `0342 cornerdl`, `0343 cornerdr`, `0344 cornerur`, `0345 cornercheckg`, `0353 cornerchecki`) implement the BNO-specific tile-edge anti-stuck logic. Each tile object owns its own corner mask; the player Step evaluates `collision_line` against the corner mask to decide whether to slide along the edge or stop.

The motion-platform check scripts (`0286 mplatbcheck`, `0287 mplatlcheck`, `0288 mplatrcheck`, `0289 mplattcheck`) handle the four sides of moving platforms — the original BNO had a small number of moving-tile rooms and these scripts are the "stand on it" predicates.

Script `0085 tileborder` is the canonical 4-edge tile boundary scan used by virtually every walkable tile: it returns whether the calling instance is at the L/R/T/B edge of its current tile. Scripts `0086-0089` (`tleftcheck`, `trightcheck`, `ttopcheck`, `tbottomcheck`) are its single-edge variants.

## Key idioms

- **`collision_rectangle` + `notme=1`** is the canonical "is anything else here?" query. Always pass `notme=1` to exclude self.
- **`mask_index` swap** is how BNO changes hitboxes mid-step without changing visual sprite. NaviMask, HexportMask, BattleMask, and SwimMask are the four masks the player rotates through.
- **Per-tile corner objects** (`hxtsideb`, `mtile1r`, etc.) own their own collision logic. The player's Step queries them via `collision_rectangle` rather than carrying corner-aware logic in the player itself.
- **`place_meeting`** is used for sentinel checks (e.g. "am I standing on grass?") where the caller doesn't need the instance ID.

## Scripts referenced in this subsystem

<!-- AUTOGEN:scripts:start -->
| Script ID | Name | Lines | Used in objects |
|-----------|------|-------|------------------|
| 0085 | tileborder | 27 | — |
| 0086 | tleftcheck | 5 | — |
| 0087 | trightcheck | 5 | — |
| 0088 | ttopcheck | 5 | — |
| 0089 | tbottomcheck | 11 | — |
| 0116 | hbordercol | 15 | — |
| 0276 | mobilecheck | 5 | — |
| 0284 | smobilecheck | 5 | — |
| 0286 | mplatbcheck | 5 | — |
| 0287 | mplatlcheck | 5 | — |
| 0288 | mplatrcheck | 5 | — |
| 0289 | mplattcheck | 5 | — |
| 0297 | imobilecheck | 5 | — |
| 0306 | tileborder_b_always | 13 | — |
| 0345 | cornercheckg | 89 | — |
| 0353 | cornerchecki | 86 | — |
| 0362 | abschecklength | 13 | — |
| 0363 | abscheckheight | 13 | — |
| 0364 | abschecklength2 | 14 | — |
| 0365 | abscheckheight2 | 14 | — |
<!-- AUTOGEN:scripts:end -->

## Objects referenced in this subsystem

<!-- AUTOGEN:objects:start -->
| Object ID | Name | Sprite | Mask | Events |
|-----------|------|--------|------|--------|
<!-- AUTOGEN:objects:end -->

## Engine functions used

<!-- AUTOGEN:gml-functions:start -->
| GML function | Call sites | Sample script | Wiki link |
|--------------|------------|---------------|-----------|
| `collision_rectangle` | 0 | — | [wiki/07-gml-core-functions](../../decomp/wiki/07-gml-core-functions.md) |
| `collision_line` | 0 | — | [wiki/07-gml-core-functions](../../decomp/wiki/07-gml-core-functions.md) |
| `place_meeting` | 0 | — | [wiki/07-gml-core-functions](../../decomp/wiki/07-gml-core-functions.md) |
| `instance_place` | 0 | — | [wiki/07-gml-core-functions](../../decomp/wiki/07-gml-core-functions.md) |
<!-- AUTOGEN:gml-functions:end -->

## Rebuild guidance

For the Phase 6 client:

- **Server-authoritative collision.** Per CLAUDE.md hard rule #1, the server resolves movement; the client predicts but never decides. Client collision is a renderer-only concern (avoid drawing the player inside a wall during predict).
- **Replace mask_index swapping with named hitboxes.** Build a `Hitbox = 'navi' | 'hexport' | 'battle' | 'swim'` enum on the player; hitbox geometry per state is a server-side table.
- **Per-tile corner logic moves to the level data.** Store corner-edge predicates in the room layer JSON (Phase 6 / 7 will derive these from `extracted/client-5-8/rooms/<NN>-<name>/tiles.json` plus the Phase 1 `RoomTile` extraction). Do NOT replicate per-corner GML scripts.
- **Use a spatial index** (broadphase grid or quadtree) on the server. Original BNO `collision_rectangle` walks every instance — fine at <50 CCU per room, breaks if you scale.

## See also

- [input.md](input.md) — what queues the movement intent that Step then collision-checks
- [scene-room-model.md](scene-room-model.md) — room boundaries trigger area transitions
- [animation.md](animation.md) — sprite/mask swap idiom
- [decomp/wiki/07-gml-core-functions.md](../../decomp/wiki/07-gml-core-functions.md) — `collision_*` reference
