# GDD: Procedural Map Generator

**System ID:** SYS-10  
**Design Order:** 10 of 33 (MVP tier — first undesigned)  
**Priority:** MVP — Core Layer  
**Dependencies:** SYS-14 (Item Database — RoomNode_SO schema)  
**Dependents:** SYS-11 (Prop System — props populate generated rooms), SYS-12 (Room Prefab Framework — editor tools for authoring rooms), SYS-09 (Loot & Extraction — spawns items into rooms), SYS-13 (Overtime Collapse — seals rooms sequentially)  
**Status:** Designed  

---

## 1. Purpose

Every match in IStalk plays in a unique building generated from modular room prefabs. There are no static maps. The Procedural Map Generator assembles a multi-floor dollhouse structure by connecting `RoomNode_SO` room modules at compatible connectors, then spawns loot, altars, and player start positions.

This system exists because:
- **Replayability** — runners can't memorize layouts, forcing real-time decision-making every match
- **The Eye's scanning challenge** — a familiar map gives runners too much advantage; procgen keeps both sides learning
- **Tools-first design** — designers create room prefabs with connector metadata; the generator does the rest. Adding new rooms = drag a new `.asset` file into the folder.

The generator runs **server-side only**. The server picks a seed, generates the layout, instantiates all room prefabs as NetworkObjects, and clients receive them through FishNet's standard spawn system.

---

## 2. Generation Algorithm

### 2.1 Seed-Based Determinism

```
Server generates seed → sends seed to all clients via RPC
Server runs generation with seed → spawns NetworkObjects
Clients see spawned objects appear (standard FishNet observer spawn)
```

The seed is an `int` (System.Random). Clients do NOT run the generator — they receive the spawned room GameObjects through FishNet's normal spawn pipeline. The seed is stored in match state for replay/debugging purposes only.

### 2.2 Grid-Based Room Placement

The generator works on a 2D grid (X = horizontal, Y = vertical/floors). Each cell is one grid unit (e.g., 4m × 3m in world space). Rooms occupy one or more cells based on their `RoomNode_SO.gridSize`.

```
Grid coordinate system:
  Y (floors)
  ^
  |  [Library 2x1]  [Hallway 1x1] [Kitchen 2x1]
  |  [Stairwell 1x2]              [Bedroom 1x1]  [Balcony 1x1]
  |  [Foyer 3x1]                  [Closet 1x1]
  +-------> X (horizontal)
```

### 2.3 Algorithm: Constrained Growth

The generator uses a growth-based approach (not pre-computed templates):

```
1. PLACE starting room at grid center (ground floor)
2. ADD all starting room's open connectors to the FRONTIER queue
3. WHILE rooms_placed < target_room_count AND frontier is not empty:
   a. POP a connector from frontier (random order, seeded)
   b. QUERY all RoomNode_SOs that have a compatible connector on the opposite side
      (e.g., frontier connector faces RIGHT → candidate must have a LEFT connector)
   c. FILTER candidates by:
      - Grid space available (no overlap with placed rooms)
      - maxPerMap limit not exceeded
      - Category constraints (see 2.4)
   d. WEIGHT candidates by RoomNode_SO.spawnWeight
   e. SELECT one candidate (weighted random, seeded)
   f. INSTANTIATE room prefab at world position calculated from grid coords
   g. REGISTER all new room's connectors that aren't consumed by this connection → add to FRONTIER
   h. MARK the consumed connector pair as "connected" (door/passage)
4. SEAL all remaining frontier connectors (wall them off — no open holes)
5. VALIDATE layout (see 2.6)
6. POPULATE (loot, altars, player spawns — see Section 4)
```

### 2.4 Category Constraints

The generator enforces structural rules to prevent degenerate layouts:

| Constraint | Rule | Why |
|-----------|------|-----|
| Minimum floors | ≥ 2 | Eye needs vertical variety for the 2D side-view to be interesting |
| Maximum floors | ≤ 4 | More than 4 floors makes scanning overwhelming |
| Stairwell requirement | At least 1 stairwell per floor connection | Runners need vertical traversal |
| Starting area | Exactly 1 per map, always ground floor | Runners need a consistent spawn area |
| Corridor ratio | ≥ 20% corridors | Prevents maps that are all rooms with no connecting tissue |
| Large room cap | ≤ 3 large rooms | Too many wide-open spaces removes cover play |
| Runner spawn distance | Runner spawn ≥ 3 rooms from any extraction altar | Prevents instant extraction |

### 2.5 Connector Matching

Connectors are defined as child Transform markers on room prefabs. Each connector has:
- **Position** — local position within the prefab
- **Direction** — which grid direction it faces (Up/Down/Left/Right from `ConnectionTag`)
- **Size** — width of the opening (doors are 1 unit, wide passages are 2 units)

Two connectors are **compatible** when:
- They face opposite directions (Left ↔ Right, Up ↔ Down)
- They have the same size
- When the candidate room is placed, its connector's world position aligns with the frontier connector's world position

### 2.6 Validation Pass

After generation completes, the server runs a validation pass:

1. **Connectivity** — flood-fill from runner spawn. All rooms must be reachable. If not → regenerate with new seed.
2. **Extraction reachability** — at least 1 extraction altar location must exist ≥ 3 rooms from spawn.
3. **Minimum room count** — if `rooms_placed < GlobalRules_SO.minRoomsPerMap` → regenerate.
4. **LoS variety** — at least 30% of rooms must have internal geometry that creates partial cover (not just empty boxes). This is guaranteed by room prefab design, not runtime checks.

If validation fails, the generator increments the seed and retries. Maximum 10 retries before falling back to a known-good seed (hardcoded as an emergency failsafe, not a designed experience).

---

## 3. World Space Layout

### 3.1 Grid-to-World Mapping

```csharp
// Grid unit dimensions (defined in GlobalRules_SO)
public float cellWidth  = 4.0f;  // metres per grid cell horizontal
public float cellHeight = 3.0f;  // metres per grid cell vertical (floor height)

// World position from grid coords
Vector3 GridToWorld(Vector2Int gridPos) =>
    new Vector3(gridPos.x * cellWidth, gridPos.y * cellHeight, 0f);
```

### 3.2 Room Instantiation

Each room prefab has its pivot at the bottom-left connector position (local origin). When placed at a grid position, the prefab's world position = `GridToWorld(gridPos)`.

Room prefabs contain:
- Static geometry (walls, floor, ceiling) — layer: Wall
- Prop spawn positions (empty transforms) — populated after generation
- Connector markers (empty transforms tagged with direction)
- Loot spawn slots (empty transforms where items can appear)
- Optional altar slot (transform for extraction altar placement)
- Optional runner spawn slot (transform for player spawn)
- Lighting fixtures (baked or real-time per art direction)

### 3.3 Sealing Unused Connectors

After all rooms are placed, any connector in the frontier that wasn't consumed gets sealed:
- Instantiate a wall segment matching the connector's size
- Place it at the connector's world position
- Set layer to Wall (blocks LoS, blocks movement)

This guarantees no open holes in the generated structure.

---

## 4. Content Population (Post-Generation)

After the room layout is generated and validated, the server populates content:

### 4.1 Loot Item Spawning

```
FOR each placed room:
  slots = room.RoomNode_SO.lootSpawnSlots
  FOR i in 0..slots:
    roll = seededRandom.NextFloat()
    IF roll < lootSpawnProbability (from GlobalRules_SO):
      item = WeightedRandomPick(lootPool, seeded)
      SpawnLootAtSlot(room, slotIndex: i, item.itemId)
```

Loot distribution rules:
- Higher-value items spawn further from runner start (distance-weighted)
- At least 1 item must spawn within 2 rooms of runner start (early grabs for engagement)
- Total spawned loot value must exceed `extractionThreshold × 2.5` (ensures extraction is possible even if runners miss some)

### 4.2 Extraction Altar Placement

- Minimum 2 altars per map (redundancy — if one is too dangerous, runners have options)
- Maximum 4 altars per map
- Only rooms with `isValidAltarLocation = true` are eligible
- Altars must be ≥ 3 rooms from runner spawn (measured in room graph distance)
- Altars must be ≥ 2 rooms apart from each other (distribute across the map)

### 4.3 Runner Spawn Placement

- All runners spawn in the same starting area room
- The starting area is always on ground floor
- Starting area has no loot (runners must venture out)
- Starting area has full cover from Eye's LoS (grace period + physical cover)

### 4.4 Incarnation Prop Seeding

- Rooms with `incarnationPropSlots > 0` get eye-decorated props placed at designated slot positions
- At least 1 incarnation prop per floor (Eye always has options)
- Incarnation props are evenly distributed (not clustered)

---

## 5. Networking

### 5.1 Authority Model

The server (Eye's machine) is the sole authority for map generation:

```
[Server] OnMatchStart():
  seed = GenerateSeed()  // int from System time or relay-provided
  layout = RunGenerator(seed)
  
  // Spawn all room prefabs as NetworkObjects
  foreach room in layout.placedRooms:
    GameObject obj = Instantiate(room.prefab, room.worldPosition, Quaternion.identity)
    ServerManager.Spawn(obj)
  
  // Spawn loot items as NetworkObjects
  foreach lootSpawn in layout.lootSpawns:
    GameObject obj = Instantiate(lootSpawn.prefab, lootSpawn.position, Quaternion.identity)
    ServerManager.Spawn(obj)
  
  // Spawn altars
  foreach altar in layout.altarPositions:
    GameObject obj = Instantiate(altarPrefab, altar.position, Quaternion.identity)
    ServerManager.Spawn(obj)
  
  // Send seed to clients (for debugging/replay only — not for client generation)
  ObserversReceiveMapSeed(seed)
```

### 5.2 Client Experience

Clients do NOT generate the map. They receive spawned objects through FishNet's standard observer pipeline. From the client's perspective:
1. Scene loads (GameScene — empty)
2. FishNet connection establishes
3. Server spawns all room objects → clients see them appear
4. Server spawns loot/altars → clients see them appear
5. Server spawns player objects → game begins

### 5.3 Bandwidth Consideration

Room prefabs are identified by their `RoomNode_SO.roomId` (string) and position (Vector3). FishNet's spawn system sends the prefab identity + transform. For a 20-room map, this is approximately 20 spawn messages at match start — negligible bandwidth (~2KB total).

---

## 6. GlobalRules_SO Parameters

All map generation constants live in `GlobalRules_SO` (per Tools First rule):

```csharp
[Header("Map Generation")]
public int minRoomsPerMap = 8;
public int maxRoomsPerMap = 30;
public int minFloors = 2;
public int maxFloors = 4;
public float corridorRatioMin = 0.20f;
public int maxLargeRooms = 3;
public int minAltars = 2;
public int maxAltars = 4;
public int altarMinDistanceFromSpawn = 3;  // room graph distance
public float lootSpawnProbability = 0.7f;  // per-slot chance
public float lootValueToThresholdRatio = 2.5f;  // total spawned ≥ threshold × this
public float cellWidth = 4.0f;
public float cellHeight = 3.0f;
```

Designers change these values in the Inspector. No code changes needed to adjust map size, density, or content distribution.

---

## 7. Editor Tooling Interface

The Procedural Map Generator relies on room prefabs being authored correctly. The Room Prefab Framework (SYS-12) provides the tools, but SYS-10 defines what it needs:

### 7.1 What Room Prefabs Must Contain

| Marker Type | Implementation | Required |
|-------------|---------------|----------|
| Connectors | Empty GameObject, child of room root, named `Connector_[Direction]_[Index]` | Yes |
| Loot spawn slots | Empty GameObject, tagged `LootSlot`, positioned where items appear | Yes (≥1 for non-corridor rooms) |
| Altar slot | Empty GameObject, tagged `AltarSlot` | Only if `isValidAltarLocation = true` |
| Runner spawn slot | Empty GameObject, tagged `RunnerSpawn` | Only if `isValidRunnerSpawn = true` |
| Incarnation prop slots | Empty GameObject, tagged `IncarnationSlot` | Optional |
| Prop spawn positions | Empty GameObjects for furniture/cover placement | Yes |

### 7.2 Runtime Validation (Editor Play Mode)

The generator can run in Editor play mode for rapid iteration:
- Press Play → generator runs with random seed
- Console logs: room count, floor count, connectivity result, loot total
- Scene view shows generated layout with gizmo lines between connectors
- Failed validation logs the specific failure reason

---

## 8. Performance Budget

| Operation | Target | Strategy |
|-----------|--------|----------|
| Generation time | < 100ms | Grid-based placement is O(rooms × candidates). 30 rooms × 20 candidates = 600 comparisons — trivial. |
| Instantiation | < 2s for full map | Use `Instantiate()` for MVP. Addressables pooling in Alpha if needed. |
| Memory | < 50MB for generated map | Room prefabs are lightweight (greybox geometry). Shared materials. |
| Network spawn burst | < 500ms | ~30 room objects + ~20 loot objects + ~3 altars = ~53 spawns. FishNet handles burst spawning efficiently. |

---

## 9. Acceptance Criteria

- [ ] Given a seed, the generator produces the same layout every time (deterministic)
- [ ] Generated maps always pass connectivity validation (all rooms reachable)
- [ ] Generated maps have 2-4 floors with stairwell connections
- [ ] Runners spawn in a starting area with full cover from Eye LoS
- [ ] Extraction altars are ≥ 3 rooms from runner spawn
- [ ] Total spawned loot value exceeds extraction threshold × 2.5
- [ ] Adding a new `RoomNode_SO` asset to Resources/ automatically includes it in generation (no code change)
- [ ] Unused connectors are sealed (no open holes in the structure)
- [ ] Clients receive the generated map through FishNet spawn (clients do NOT run generator)
- [ ] Generation completes in < 100ms on target hardware
- [ ] Designer can adjust all generation parameters via GlobalRules_SO in Inspector

---

## 10. Relationship to Other Systems

| System | Relationship |
|--------|-------------|
| **SYS-14 Item Database** | Provides `RoomNode_SO` schema and `GlobalRules_SO` parameters |
| **SYS-12 Room Prefab Framework** | Provides the editor tools for authoring room prefabs with connectors |
| **SYS-11 Prop System** | Props are placed at designated slots within room prefabs |
| **SYS-09 Loot & Extraction** | Loot items spawn at loot slots; altars spawn at altar slots |
| **SYS-05 Geometric LoS** | Room walls and props block raycasts — generation doesn't affect LoS logic |
| **SYS-22 Match State Machine** | Generation triggers during the Loading phase, before Grace period |
| **SYS-13 Overtime Collapse** | Seals rooms in reverse-distance order from extraction altars |
| **SYS-33 FishNet Replication** | Rooms, loot, altars are all NetworkObjects spawned by server |

---

## 11. Future Extensions (Alpha+)

- **Branching corridors** — non-linear layouts with loops and alternate paths
- **Themed floor generation** — each floor has a category (domestic, industrial, gothic) affecting room pool
- **Dynamic difficulty** — more rooms / more floors for higher player counts
- **Replay seed sharing** — players can share seeds for challenge runs
- **Editor preview tool** — generate maps in Edit mode without entering Play mode
