# ADR-0006: Named Physics Layer Architecture

**Status:** Accepted  
**Date:** 2026-06-23  
**Deciders:** Mahmoud Aglan  
**Engine:** Unity 6  

---

## Context

Multiple systems in IStalk use physics queries (raycasts, sphere casts, overlap checks) that must target specific object categories. Without a deliberate layer architecture, systems will use ad-hoc masks that conflict, miss objects, or hit things they shouldn't.

Key physics interactions from the GDDs:
- LoS raycasts (SYS-05): Must hit walls, props, and smoke volumes — must NOT hit Runners, triggers, or the Eye.
- Laser SphereCast (SYS-06): Must hit Runners and props — must NOT hit walls behind the target, smoke, or the Eye's own colliders.
- Wall detection for wall-run/slide (SYS-01): Must hit walls only.
- Runner-prop collision (SYS-11): `OnControllerColliderHit` fires when Runner's CharacterController hits any collider — prop layer lets us identify prop impacts specifically.
- Grapple anchor detection (SYS-01): Must find grapple points specifically.

Unity provides layers 0-31. Layers 0-7 are Unity-reserved. We have layers 8-31 available (24 slots).

---

## Decision

**8 named gameplay layers, assigned to Unity layers 8-15. Layer collision matrix configured to minimize unnecessary physics interactions.**

| Unity Layer Index | Name | Contains |
|-------------------|------|----------|
| 8 | **Wall** | All static level geometry (floors, walls, ceilings, stairs, door frames) |
| 9 | **Prop** | All prop colliders — static props, dynamic/slideable props, incarnation props, fragile props |
| 10 | **Runner** | Runner's CharacterController collider |
| 11 | **Eye** | Eye's camera mount (for NetworkTransform position sync, not collision) — rarely needs physics |
| 12 | **SmokeVolume** | Smoke bomb area-of-effect trigger colliders (blocks LoS raycast but not movement) |
| 13 | **GrappleAnchor** | Grapple hook anchor points (trigger colliders, detected by overlap sphere) |
| 14 | **Trigger** | Generic interaction triggers (extraction altars, item pickup radius, shrine interact zones) |
| 15 | **EyeBeam** | Laser telegraph LineRenderer collider (if needed for visual hit detection on client) — currently unused, reserved |

---

## Layer Masks Per System

### LoSManager (SYS-05) — Server Raycast at 30Hz

```csharp
// Blocks visibility: walls, props, smoke
private static readonly int LosMask = LayerMask.GetMask("Wall", "Prop", "SmokeVolume");

// Usage: Physics.Raycast(eyeSocket, dirToRunner, out hit, maxDist, LosMask)
// If hit.distance < distance-to-runner → Runner is NOT visible (something blocks)
// If no hit or hit.distance >= distance-to-runner → Runner IS visible
```

**Why SmokeVolume blocks LoS:** Smoke bombs (Alpha — SYS-16 Usable Items) create temporary visibility-blocking zones. Including them in the LoS mask now means no code change is needed when smoke bombs are implemented.

---

### EyeLaserController (SYS-06) — Server SphereCast on Fire

```csharp
// Hits: runners and props (stops at first prop hit, can kill runners behind transparent cover is NOT a thing)
private static readonly int LaserMask = LayerMask.GetMask("Runner", "Prop");

// Usage: Physics.SphereCastAll(origin, 0.35f, direction, 80f, LaserMask)
// Sort by distance, process first hit:
//   - If Prop: deal damage to prop, stop
//   - If Runner: validate LoS + invulnerability, eliminate
```

**Why no Wall in laser mask:** The laser originates from the Eye's camera (outside the house) and fires into the cutaway interior. Walls are the structural boundary — the laser never hits them because it enters through the cutaway face. Props inside the house DO block the laser.

**IncarnationProp sub-behavior:** When the Eye is incarnated into a prop (Alpha — SYS-07), the laser fires FROM inside the house. In this mode, `GetLaserMask()` returns a modified mask that INCLUDES Wall (the incarnated eye can hit walls) but EXCLUDES the incarnated prop's own collider.

---

### RunnerController (SYS-01) — Wall Detection

```csharp
// Wall-run and wall-slide detection
private static readonly int WallMask = LayerMask.GetMask("Wall");

// Usage: Physics.SphereCast(position, 0.4f, moveDir, out hit, 0.6f, WallMask)
// If hit → eligible for wall-run/wall-slide
```

---

### RunnerController (SYS-01) — Ground Check

```csharp
// Ground detection for landing, coyote time
private static readonly int GroundMask = LayerMask.GetMask("Wall", "Prop");

// Usage: Physics.SphereCast(position, ccRadius, Vector3.down, out hit, 0.1f, GroundMask)
// Both walls (floors) and props (standing on a crate) count as ground
```

---

### GrappleHook (SYS-01 ability) — Anchor Detection

```csharp
private static readonly int GrappleMask = LayerMask.GetMask("GrappleAnchor");

// Usage: Physics.OverlapSphere(aimPoint, searchRadius, GrappleMask)
// Returns nearest valid grapple anchor
```

---

### RunnerInteractor — Interaction Detection

```csharp
private static readonly int InteractMask = LayerMask.GetMask("Trigger");

// Usage: Physics.OverlapSphere(runnerPos, interactRadius, InteractMask)
// Returns extraction altars, item pickups, shrine interact zones in range
```

---

## Collision Matrix Configuration

Unity's Layer Collision Matrix controls which layers generate collision events with each other. Disabling unnecessary pairs improves physics performance.

| | Wall | Prop | Runner | Eye | Smoke | Grapple | Trigger | EyeBeam |
|---|---|---|---|---|---|---|---|---|
| **Wall** | - | - | ✅ | - | - | - | - | - |
| **Prop** | - | - | ✅ | - | - | - | - | - |
| **Runner** | ✅ | ✅ | - | - | - | - | ✅ | - |
| **Eye** | - | - | - | - | - | - | - | - |
| **SmokeVolume** | - | - | - | - | - | - | - | - |
| **GrappleAnchor** | - | - | - | - | - | - | - | - |
| **Trigger** | - | - | ✅ | - | - | - | - | - |
| **EyeBeam** | - | - | - | - | - | - | - | - |

**Key decisions:**
- Runner ↔ Wall: ✅ — CharacterController needs collision response.
- Runner ↔ Prop: ✅ — `OnControllerColliderHit` detects Runner bumping dynamic props (SYS-11).
- Runner ↔ Trigger: ✅ — `OnTriggerEnter`/`Stay`/`Exit` for pickup/extraction/shrine proximity.
- Runner ↔ Runner: ❌ — Runners do not collide with each other (they can overlap/pass through).
- Eye ↔ anything: ❌ — Eye has no physics collider interactions. Its "collider" is just a NetworkTransform position marker.
- SmokeVolume: ❌ collision with everything — it's only used as a raycast target for LoS blocking, never for collision response.
- GrappleAnchor: ❌ collision — only used in `OverlapSphere` queries with explicit mask.

**Note:** Raycasts and SphereCasts use explicit `LayerMask` parameters and are NOT affected by the collision matrix. The matrix only governs `OnCollision`/`OnTrigger` callbacks from the physics engine. Both systems are configured correctly: matrix for physics callbacks, masks for queries.

---

## Consequences

**Positive:**
- Every physics query in the codebase has a single, well-defined mask — no ad-hoc magic numbers.
- Collision matrix is minimal — only 3 active pairs, reducing physics overhead.
- SmokeVolume in LoS mask future-proofs for Alpha without code changes.
- Clear separation: Runners can stand on props (GroundMask) but the laser hits through the house exterior (no Wall in LaserMask).
- GrappleAnchor and Trigger layers prevent false positives in interaction queries.

**Negative:**
- 8 of 24 available custom layers consumed. 16 remain for future needs (sufficient).
- All level designers must understand which layer to assign geometry to. Mitigation: enforce via editor tooling or naming conventions on prefabs.
- Prop layer encompasses multiple prop sub-types (static, dynamic, fragile, incarnation). Differentiation is by component (`PropBase` subclass), not layer. This means laser SphereCast hits ALL props equally — sub-type logic runs after the hit.

---

## GDD Requirements Addressed

- TR-PHY-005: 8 named layers (Wall, Prop, Runner, Eye, SmokeVolume, GrappleAnchor, Trigger, EyeBeam)
- TR-PHY-001: LoS via Physics.Raycast with explicit mask (Wall+Prop+SmokeVolume)
- TR-PHY-002: Laser via Physics.SphereCastAll with mask (Runner+Prop)
- TR-PHY-003: Wall detection via SphereCast with WallMask
- TR-PHY-004: OnControllerColliderHit — Runner↔Prop collision enabled in matrix

---

## References

- `design/gdd/sys-05-geometric-los.md` — LoS raycast specification
- `design/gdd/sys-06-eye-laser.md` — Laser SphereCast specification
- `design/gdd/sys-01-runner-movement.md` — Wall detection, ground check, grapple
- `design/gdd/sys-11-prop-system.md` — Prop collision types
