---
description: Space reservation collision detection. Temporal layouts. No double-booking.
globs: ["app/Domain/Facility/**", "app/Domain/Scheduling/**"]
---

# Space & Collision Detection

## Temporal Layouts

A facility can have DIFFERENT layouts at different times:
- Morning: 3×2 grid (small areas for kids)
- Evening: 2×1 grid (large areas for adults)

Layout identified by: `facility_id + day_of_week + start_time + end_time`

## Layout Overlap Rule

No two layouts for the same facility can overlap in time on the same day.
`is_recurring = true` with specific `effective_day_of_week` vs `is_recurring = false` with specific `effective_date`:
- Specific date OVERRIDES recurring for that date

## Collision Detection Algorithm

```php
SpaceCollisionService::check($facilityId, $date, $startTime, $endTime, $segmentIds, $excludeId = null)

1. Find CONFIRMED reservations for facility on date
2. Filter overlapping time: WHERE start_time < $endTime AND end_time > $startTime
3. Exclude self (if editing): WHERE id != $excludeId
4. Decode segments JSON from each overlapping reservation
5. Intersect: requested segments ∩ occupied segments
6. Empty intersection → no collision (OK)
7. Non-empty → return conflict details
```

## Reservation Rules

- `confirmed` reservations block others (hard)
- `tentative` reservations warn but don't block
- `cancelled` reservations are invisible to collision detection
- Cannot reserve unavailable segments (`is_available = false`)
- Cannot reserve outside facility operating hours
- Cannot reserve when facility status != 'active'

## Session → Reservation Link

When session is created:
1. Check if group has assigned space (via group_schedule)
2. If yes: auto-create reservation for the session's time + segments
3. If collision: FAIL session creation, report conflict

When session is cancelled:
- Linked reservation.status → 'cancelled'
- Segments immediately available for others

## Layout Types → Segment Auto-Generation

- `grid(rows=3, columns=2)` → 6 segments: R1C1, R1C2, R2C1, R2C2, R3C1, R3C2
- `lanes(lane_count=6)` → 6 segments: Lane 1, Lane 2, ..., Lane 6
- `zones(definitions=[...])` → one segment per zone definition object
- `custom(definitions=[...])` → same as zones, no geometric constraints
