EditorGUILayout.HelpBox("Test procedural map generation without entering Play mode. Use to validate room pool and GlobalRules settings.",MessageType.None);
> **Current State:** 69 C# scripts written, FishNet v4 + Synapse installed, DOTween installed, URP configured. **Nothing exists in the Unity Editor yet** — no prefabs, no scenes, no ScriptableObject assets, no materials, no physics layers. This guide takes you from zero to playable greybox.
> **Current State:** 80+ C# scripts written (including Eye Types, Wearables, Co-op, Extraction Room systems), FishNet v4 + Synapse installed, DOTween installed, URP configured. **Nothing exists in the Unity Editor yet** — no prefabs, no scenes, no ScriptableObject assets, no materials, no physics layers. This guide takes you from zero to playable greybox.
---
---
...
@@ -9,16 +9,17 @@
...
@@ -9,16 +9,17 @@
```
```
HAVE (code-only) NEED (Unity Editor work)
HAVE (code-only) NEED (Unity Editor work)
───────────────── ────────────────────────
───────────────── ────────────────────────
69 .cs scripts 2 Scenes (Lobby + Game)
80+ .cs scripts 2 Scenes (Lobby + Game)
Assembly definitions Physics layers configured
Assembly definitions Physics layers configured
Folder structure Prefabs (15+)
Folder structure Prefabs (18+)
FishNet v4 + Synapse transport ScriptableObject assets (15+)
FishNet v4 + Synapse transport ScriptableObject assets (30)
URP pipeline settings Materials (greybox)
URP pipeline settings Materials (greybox)
DOTween NetworkManager setup
DOTween NetworkManager setup
Canvas prefabs (3 HUDs)
Custom editor inspectors Canvas prefabs (4 HUDs)
Input System bindings
Eye Type system (3 types) Input System bindings
Room prefabs (5)
Wearable system (6 abilities) Room prefabs (5)
Build Settings configured
Co-op mechanics Extraction Room prefab
Extraction Room system Build Settings configured
```
```
---
---
...
@@ -203,7 +204,53 @@ Create in `Assets/Resources/Items/Traps/`:
...
@@ -203,7 +204,53 @@ Create in `Assets/Resources/Items/Traps/`:
│ ├── EyeEyelidController.cs ← NEW: right-click toggle, fullscreen black overlay
│ ├── EyeFlashReceiver.cs ← NEW: receives flash, checks eyelid state
│ └── AudioSource
│ └── AudioSource
├── EyeCamera (Camera component)
├── EyeCamera (Camera component)
│ ├── Projection: Orthographic
│ ├── Projection: Orthographic
...
@@ -381,10 +434,12 @@ Eye (Empty)
...
@@ -381,10 +434,12 @@ Eye (Empty)
**Steps:**
**Steps:**
1. Create Empty → name "Eye"
1. Create Empty → name "Eye"
2. Add `NetworkObject`
2. Add `NetworkObject`
3. Add Eye scripts listed above
3. Add Eye scripts listed above (including 3 new ones)
4. Create child Camera "EyeCamera" → Orthographic, Size 20, Rotation (90,0,0), Position (0,50,0)
4. Create child Camera "EyeCamera" → Orthographic, Size 20, Rotation (90,0,0), Position (0,50,0)
5. Drag into `Assets/Prefabs/Player/Eye.prefab`
5. Drag into `Assets/Prefabs/Player/Eye.prefab`
> **EyeTypeController** dynamically enables/disables Laser/Scan/Traps/Incarnation/Eyelid scripts at spawn based on which EyeType_SO asset is selected. The Eye player picks their type in lobby.
---
---
## Phase 7: Prop & Item Prefabs (20 min)
## Phase 7: Prop & Item Prefabs (20 min)
...
@@ -476,6 +531,45 @@ DynamicProp (Empty)
...
@@ -476,6 +531,45 @@ DynamicProp (Empty)
Save as `Assets/Prefabs/Props/DynamicProp.prefab`
Save as `Assets/Prefabs/Props/DynamicProp.prefab`
### 7.6 Extraction Room Prefab (NEW)
Use `IStalk > Create > Extraction Room Prefab Setup` or build manually:
```
ExtractionRoom (Empty)
├── NetworkObject
├── ExtractionRoom.cs
├── AntiAfkRitual.cs
├── BoxCollider (trigger, size 15x4x15, center 0,2,0)
Save as `Assets/Prefabs/Props/ExtractionRoom.prefab`
> **Important:** The room must be dense with incarnation props (Eye can possess them during the 30s extraction). Only ONE entrance so runners can be trapped.
│ └── FlashCooldown (TMP, "Flash CD: 12s", shows team cooldown)
├── Minimap Panel (RawImage, top-right corner, for RenderTexture)
├── Minimap Panel (RawImage, top-right corner, for RenderTexture)
└── GraceIndicator (TMP, "GRACE PERIOD - 0:30", yellow, hidden after grace)
└── GraceIndicator (TMP, "GRACE PERIOD - 0:30", yellow, hidden after grace)
```
```
Add `EyeHUD.cs` component. Wire references.
Add `EyeHUD.cs` component. Wire references.
> **Note:** Ability slots should be shown/hidden based on EyeType. A Watcher won't see the Laser slot. A Trapper won't see the Laser slot either. EyelidIndicator only shows if `hasEyelids = true`.
@@ -41,10 +41,10 @@ Every match tightens. Dead Runners become minions. The Eye grows stronger. Extra
...
@@ -41,10 +41,10 @@ Every match tightens. Dead Runners become minions. The Eye grows stronger. Extra
**Design test:** "Can this mechanic create a stalemate?" → If yes, add a timer, escalation trigger, or pressure valve that breaks the deadlock.
**Design test:** "Can this mechanic create a stalemate?" → If yes, add a timer, escalation trigger, or pressure valve that breaks the deadlock.
### Pillar 5: Paranoia is a Feature
### Pillar 5: Together or Dead
Other Runners are unreliable. Objects might be watching. Safe rooms might be trapped. Trust is a resource that depletes. The social and environmental design should make players second-guess everything.
Runners who coordinate survive. The game rewards proximity, timing, and sacrifice. Solo play is viable but the optimal path always involves teammates. The Eye exploits separation — clustered runners are harder to isolate but easier to area-deny.
**Design test:** "Is this interaction too predictable?" → If a Runner can always trust X, add a reason they might not be able to.
**Design test:** "Can a solo runner accomplish this equally well?" → If yes, add a co-op bonus that makes the team version faster/safer.
---
---
...
@@ -52,7 +52,7 @@ Other Runners are unreliable. Objects might be watching. Safe rooms might be tra
...
@@ -52,7 +52,7 @@ Other Runners are unreliable. Objects might be watching. Safe rooms might be tra
1.**NOT a precision shooter** — The Eye's skill is strategic, not mechanical aim.
1.**NOT a precision shooter** — The Eye's skill is strategic, not mechanical aim.
2.**NOT a survival horror resource game** — No health packs, ammo, or crafting. Parkour flow stays clean.
2.**NOT a survival horror resource game** — No health packs, ammo, or crafting. Parkour flow stays clean.
3.**NOT a team game with roles** — No Tank/Healer/DPS archetypes. All Runners have the same toolkit. Trust is optional.
3.**NOT a class-based team game** — No fixed roles or forced compositions. Any runner can equip any wearable. Teamwork is rewarded, not required for basic play.
4.**NOT a battle royale with shrinking zones** — Escalation is organic (minions + economy), not artificial.
4.**NOT a battle royale with shrinking zones** — Escalation is organic (minions + economy), not artificial.
5.**NOT a content-heavy live-service grind** — Replayability from procedural maps and player dynamics, not seasonal content.
5.**NOT a content-heavy live-service grind** — Replayability from procedural maps and player dynamics, not seasonal content.
### Heavy Modifications (significant logic changes)
| File | Changes |
|------|---------|
| `Runner/RunnerController.cs` | **Strip all advanced movement.** Remove: dash, wall-run, air-dash, slide, vault logic. Keep: run, sprint, jump, crouch, coyote time, jump buffer, gravity. Add: public methods `EnableAbility(WearableAbilityType)` / `DisableAbility(WearableAbilityType)` that re-enable the stripped code paths conditionally. Each movement ability becomes a guarded block: `if (_dashEnabled) { /* existing dash code */ }` |
| `Runner/PlayerInventory.cs` | **Add wearable slot system.** New `SyncList<string> _wearableIds` (max 2). New methods: `TryEquipWearable(string id)`, `TryUnequipWearable(string id)`, `DropAllWearables(Vector3 pos)`. Modify `DropAll()` to also call `DropAllWearables()`. Add `OnWearableChanged` event. |
| `Core/ExtractionManager.cs` | **Replace per-player extraction with room-based.** Remove `_extractedPlayerIds` SyncList and `TryExtract(NetworkObject)`. Add: `SyncVar<bool> _roomUnlocked`, `SyncVar<float> _extractionTimer`, method `UnlockRoom()` (called when threshold met), server Update that ticks timer when all alive in room. Fire `OnAllExtracted` event. |
| `Core/GameManager.cs` | **Update win condition.** Subscribe to `ExtractionManager.OnAllExtracted` instead of individual `OnPlayerExtracted`. Remove individual extraction counting. Add Eye type selection relay from lobby. Update GracePeriod: Eye places traps (unchanged). |
| `Runner/RunnerInteraction.cs` | **Add co-op interactions.** Add: boost detection (scan for crouching runners near walls), throw targeting (aim at teammate while holding G), extraction room entry logic. Remove: any betrayal-related checks if they exist. |
| `Core/Shrine.cs` | **Expand stock categories.** Add wearable items and co-op buffs to `PopulateStock()`. Split UI display into categories: Loot Items / Wearables / Co-op Buffs. Add purchase validation for wearable slot availability. |
| `Data/GlobalRules_SO.cs` | **Add all new tuning constants** from Part E of design doc (eye type multipliers, wearable cooldowns, co-op values, extraction room timers). |
| `Runner/ShrineItemEffect.cs` | **Add Flash Orb effect.** When activated, fires `[ServerRpc] ServerTriggerFlash()` which broadcasts to Eye's `EyeFlashReceiver`. Add team cooldown check. |
| `Eye/EyeLaserController.cs` | **Accept stat overrides.** Add `public void ApplyTypeModifiers(float cooldownMult, float radiusMult)` called by `EyeTypeController` at spawn. Use modified values instead of raw `GlobalRules_SO` values. |
| `Eye/EyeTrapPlacer.cs` | **Accept stat overrides.** Add `public void ApplyTypeModifiers(float costMult, int maxActiveBonus)`. |
### Light Modifications (small additions)
| File | Changes |
|------|---------|
| `Eye/DreadEconomy.cs` | Add `public void ApplyStartOverride(float amount)` and `public void ApplyRegenOverride(float rate)` for eye type stat customization. |
| `Eye/EyeIncarnationController.cs` | No changes needed — works as-is in extraction room (just needs dense props placed by MapGenerator). |
| `Runner/RunnerHealth.cs` | On `_isDown` change: call `RunnerWearableManager.DropAllWearables()`. Add reference to wearable manager. |
| `Core/MapGenerator.cs` | Add extraction room generation: a locked room with dense `IncarnationProp` spawns, unlocked via `ExtractionManager`. |
| `Core/LobbyManager.cs` | Add Eye type selection UI hook. Sync selected `EyeType_SO` ID to all clients before match start. Pass to `PlayerSpawner`. |
| `Core/PlayerSpawner.cs` | When spawning Eye: read selected type, pass to `EyeTypeController` initialization. |
| `Shared/WeightComponent.cs` | No changes — wearables are weightless by design. |
| `Runner/RunnerVisibility.cs` | No changes needed. |
---
## Scripts to Remove / Deprecate
None. All existing code is preserved — advanced movement code in `RunnerController` is guarded behind flags, not deleted. This allows wearables to re-enable the exact same physics code.
**Alternative to 3 prefabs:** Single Eye prefab with `EyeTypeController` that enables/disables scripts at runtime based on the `EyeType_SO`. This is cleaner — one prefab, dynamic configuration.
**Recommended approach:** Single Eye prefab. `EyeTypeController.OnStartServer()` reads the assigned `EyeType_SO` and:
38.`UI/EyeTypeSelectionUI.cs` (new) — lobby eye type picker
---
## Network Considerations
| Concern | Solution |
|---------|----------|
| Eye type must be known before spawn | Sync eye type ID in lobby phase via `LobbyManager` relay message. `PlayerSpawner` reads it. |
| Wearable equip/unequip must be server-authoritative | All equip/unequip via `[ServerRpc]`. Client sends request, server validates slot availability. |
| Flash must be validated server-side | `ShrineItemEffect` fires `ServerRpc` → server checks team cooldown → server fires flash to Eye's `EyeFlashReceiver` |
| Eyelid state must be checked server-side | `EyeFlashReceiver` checks `EyeEyelidController._eyelidsClosed` SyncVar on server when flash arrives |
| Extraction room presence must be server-authoritative | `ExtractionRoom` uses server-side trigger colliders (`OnTriggerEnter/Exit` with `[Server]` guard) |
| Boost/throw must prevent exploits | Server validates distance, state (crouching/alive), and cooldowns before executing |
| Anti-AFK must prevent abuse | Server requires majority vote (2+ of remaining alive runners), 20s uninterruptible channel |
---
## Editor Tooling Additions
| Tool | Purpose |
|------|---------|
| `Editor/EyeTypeEditor.cs` | Custom inspector for `EyeType_SO` — preview which abilities are enabled, stat modifiers visualized |
The Eye is no longer a single archetype. Three distinct Eye types offer different playstyles, each built from the existing ability pool (Laser, Scan, TrapPlacer, Incarnation). Eye types are defined by `EyeType_SO` ScriptableObjects and selected in the lobby before match start.
### The 3 Eye Types
| Type | Primary Ability | Has Eyelids | Unique Trait |
- A "flash dodge window" indicator pulses if flash is incoming (audio cue only, no visual cheat)
**Flash interaction:**
- If flash hits while eyelids are OPEN → full blind for `flashDuration` seconds (white overlay, lose all vision + abilities)
- If flash hits while eyelids are CLOSED → completely dodged (0 effect)
- The Eye must predict when a flash is coming and commit to blindness preemptively
**Network sync:**
-`SyncVar<bool> _eyelidsClosed` on `EyeEyelidController`
- Server validates: if `_eyelidsClosed == true` when flash RPC arrives, flash is negated
- Runners see a visual indicator on the Eye (pupil narrows/closes) so they know the state
### Flash System (New)
**Source:** Shrine consumable item ("Flash Orb")
- Purchased at shrines using loot (costs defined in `ShrineItemData_SO`)
- One-use: activates immediately on use (Q key through existing `ShrineItemEffect`)
- Affects the Eye globally (not proximity-based — it's a flash against a giant eye in the sky)
- Flash duration: 4-6 seconds (tunable in `GlobalRules_SO`)
- Cooldown per team: 15 seconds (prevents flash-spam)
---
## Part B: Wearable Skill System
### Overview
Runners no longer have innate advanced movement. All parkour abilities (dash, wall-run, air-dash, slide, vault, grapple) are **wearable items** found in the world or purchased at shrines. This transforms the core loop: start basic → acquire skills → build your loadout.
### Base Runner (No Wearables)
- Run (8 m/s)
- Sprint (8 × 1.4 = 11.2 m/s, costs stamina)
- Jump (single, with coyote time + jump buffer)
- Crouch
- That's it. No dash, no wall-run, no slide, no vault, no air-dash.
### Wearable Slots
-**2 dedicated wearable slots** (separate from loot inventory)
- Wearables are **weightless** (do not affect WeightComponent)
- Wearables persist until manually unequipped or player is downed
- On death/down: wearables DROP as world items (other runners can pick them up)
### Skill Wearables (Initial Set)
| Wearable | Ability | Cooldown | Found/Bought |
|----------|---------|----------|--------------|
| Bracelet of Dash | Ground dash (5m, 0.2s) | 3s | World loot |
| Wall-Run Boots | Wall-run (min speed 4 m/s) | Passive (no CD) | World loot |
| Grappling Hook | Aim + fire grapple to surfaces | 8s | Shrine purchase (expensive) |
### Co-op Buff Items (Shrine Purchases)
| Item | Effect | Duration | Radius |
|------|--------|----------|--------|
| Linked Sprint Totem | +20% speed for all runners within 5m of holder | 30s | 5m |
| Flash Orb | Blinds the Eye for 5s (dodgeable with eyelids) | Instant | Global |
| Silence Shroud | Suppresses all noise from holder + nearby runners | 20s | 4m |
| Vitality Link | Nearby downed runners auto-revive over 10s | 10s | 3m |
### Inventory Architecture
```
PlayerInventory (existing)
├── _lootSlots: SyncList<string> (unchanged — loot items for extraction)
├── _wearableSlots: SyncList<string> (NEW — max 2, skill items)
└── _activeBuffs: SyncList<string> (NEW — timed co-op buffs, auto-expire)
```
---
## Part C: Co-op Mechanics
### Betrayal Removed
The "Paranoia" pillar is REPLACED with a new co-op pillar. Runners cannot hurt each other. All systems that allowed betrayal are removed.
### New Pillar: "Together or Dead"
Runners who work together move faster, reach more places, and survive longer. Solo play is viable but suboptimal — the game rewards proximity and coordination without forcing it.
**Design test:** "Can a solo runner do this equally well?" → If yes, add a co-op bonus or make the solo path harder/slower.
### Mechanic 1: Ledge Boost
**Setup:** One runner crouches against a wall at a high ledge. Another runner approaches and presses F.
2. Boosting runner stays in place (vulnerable, crouched)
3. Creates access to high routes, shortcuts, hidden loot rooms
**Network flow:**
- Client presses F near crouching teammate + wall → `[ServerRpc] ServerRequestBoost(NetworkObject booster)`
- Server validates: booster is crouching, near wall, both alive, within range
- Server triggers boost physics on the requesting runner
-`[ObserversRpc]` plays boost VFX/SFX for all clients
### Mechanic 2: Bag Throw
**Setup:** Runner holds G (instead of tap to drop) while looking at a teammate within range.
**Execution:**
1. Item arcs toward targeted teammate
2. Range scales inversely with item weight (light items throw far, heavy items barely lob)
3. If teammate has inventory space, auto-pickup. If not, drops at their feet.
**Network flow:**
- Client holds G + aims at teammate → `[ServerRpc] ServerThrowItem(string itemId, NetworkObject target)`
- Server validates: item exists in inventory, target is alive runner, within max throw range
- Server removes from sender inventory, spawns temporary projectile
- On arrival: `TryAddItem` on target. If fails, spawn `WorldItem` at target position.
### Mechanic 3: Linked Sprint (Buff Item)
Purchased at shrine. When activated, creates a 5m aura around the holder for 30 seconds:
- All runners in radius get +20% sprint speed
- If ANY runner in the link gets hit by the Eye, ALL linked runners get a 1-second slow
- Creates a risk/reward clustering dynamic
- Visible tether VFX between linked runners
---
## Part D: Extraction Rework
### Overview
Extraction no longer works per-player. ALL alive runners must gather in the **Extraction Room** and survive 30 seconds together to escape.
### Flow
1. Team deposits loot at altars throughout the match (unchanged)
2. When `_teamLootDeposited >= extractionThreshold`, the Extraction Room unlocks
3. ALL alive runners must enter the Extraction Room
4. A 30-second countdown begins when all alive runners are inside
5. If ANY alive runner leaves the room, countdown pauses/resets
6. After 30 seconds: all runners in the room extract. Downed runners left behind.
### The Extraction Room
- A special room generated by `MapGenerator` (always present, locked until threshold met)
-**Dense with incarnation props** (eye paintings, gargoyles, mirrors) — the Eye can incarnate freely
- Eye objects are NOT new mechanics — they're existing `IncarnationProp` components at high density
- The room is a pressure cooker: runners move freely but the Eye has many attack vectors
### Anti-AFK / Griefer System
**Problem:** One alive player refuses to enter the room, blocking extraction.
**Solution:** Kill Ritual
1. 2+ runners can initiate a "Vote Kick" ritual near an altar (hold R together for 20s)
2. Target runner gets a warning ("Your team is voting to sacrifice you — return to group!")
3. After 20 seconds: target is killed (set to downed state, drops loot)
4. Target no longer counts as "alive" for extraction purposes
5. Requires majority of OTHER alive runners to participate
### ExtractionManager Changes
```csharp
// OLD: per-player extraction
TryExtract(NetworkObjectplayer)
// NEW: room-based all-at-once
SyncVar<bool>_extractionRoomUnlocked
SyncVar<float>_extractionTimer(30scountdown)
SyncVar<int>_runnersInRoom
// Server Update: if all alive runners in room && timer > 0, tick down
// When timer hits 0: extract all runners in room
```
---
## Part E: GlobalRules_SO Additions
```csharp
[Header("Eye Types")]
publicfloatflashDuration=5f;
publicfloatflashTeamCooldown=15f;
publicfloatwatcherScanCooldownMultiplier=0.6f;
publicfloatpredatorLaserCooldownMultiplier=0.5f;
publicfloattrapperDreadCostMultiplier=0.6f;
publicinttrapperMaxActiveBonus=3;
[Header("Wearables")]
publicintmaxWearableSlots=2;
publicfloatdashCooldown=3f;
publicfloatairDashCooldown=5f;
publicfloatgrappleCooldown=8f;
publicfloatthrowRangeBase=8f;
publicfloatthrowRangeWeightScale=0.5f;
[Header("Co-op")]
publicfloatboostHeight=2f;
publicfloatboostRange=1.5f;
publicfloatlinkedSprintBonus=0.2f;
publicfloatlinkedSprintRadius=5f;
publicfloatlinkedSprintSlowDuration=1f;
[Header("Extraction Room")]
publicfloatextractionChannelDuration=30f;
publicfloatantiAfkRitualDuration=20f;
publicintantiAfkMinVoters=2;
```
---
## Part F: Pillar Updates
### REMOVED: Pillar 5 — "Paranoia is a Feature"
### NEW: Pillar 5 — "Together or Dead"
Runners who coordinate survive. The game rewards proximity, timing, and sacrifice. Solo play is viable but the optimal path always involves teammates. The Eye exploits separation — clustered runners are harder to isolate but easier to area-deny.
**Design test:** "Can a solo runner accomplish this equally well?" → If yes, add a co-op bonus that makes the team version faster/safer.
### UPDATED: Anti-Pillar 3
**OLD:** "NOT a team game with roles — No Tank/Healer/DPS archetypes. All Runners have the same toolkit. Trust is optional."
**NEW:** "NOT a class-based team game — No fixed roles or forced compositions. Any runner can equip any wearable. Teamwork is rewarded, not required for basic play."
---
## Part G: New ScriptableObjects Required
| Asset | Path | Purpose |
|-------|------|---------|
| `EyeType_SO` | `Assets/Scripts/Data/EyeType_SO.cs` | Defines an Eye type (abilities, eyelid, stat overrides) |
Returns full project with all boards, lists, labels, members.
### Update Project
```bash
PATCH /api/projects/:id
Content-Type: application/json
{
"name": "Updated Name",
"description": "Updated description"
}
```
### Delete Project
```bash
DELETE /api/projects/:id
```
---
## Project Managers
### Add Manager to Project
```bash
POST /api/projects/:projectId/project-managers
Content-Type: application/json
{
"userId": "<user-id>"
}
```
### Remove Manager
```bash
DELETE /api/project-managers/:id
```
---
## Boards
### Create Board
```bash
POST /api/projects/:projectId/boards
Content-Type: application/json
{
"name": "Sprint 1",
"position": 1
}
```
### Get Board (with all lists, cards, labels)
```bash
GET /api/boards/:id
```
Returns full board data including `included.lists[]`, `included.cards[]`, `included.labels[]`, `included.cardMemberships[]`, `included.cardLabels[]`, `included.tasks[]`, `included.taskLists[]`