# Spore Overlay Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** When the DM toggles a player to `spore_active`, that player sees a fullscreen screen-space shader effect (green tint, vignette, subtle distortion). Toggling back to `default` removes it.

**Architecture:** A new `shaders/spore_overlay.gdshader` (CanvasItem shader sampling the screen texture) is applied to a fullscreen ColorRect on a CanvasLayer. `NetworkManager` emits a new `overlay_changed` signal from the existing `set_overlay` RPC. `tavern.gd._setup_spore_overlay()` creates the overlay UI and connects to that signal. DM clients skip this (early return in `_ready()`). Intensity is binary (0.0 / 1.0) in this plan — Schritt 11 adds continuous slider control.

**Tech Stack:** Godot 4.x GDScript, GLSL (Godot canvas_item shader), CanvasLayer, ShaderMaterial

---

## Context

Existing RPC chain (already implemented):
```
DM: request_set_overlay.rpc_id(1, peer_id, "spore_active")
  → Server validates DM role, calls set_overlay.rpc_id(peer_id, "spore_active")
    → Player client: GameState.set_overlay_local(my_id, "spore_active")
```

`set_overlay` in `network_manager.gd` currently only calls `GameState.set_overlay_local` — no visual effect fires. This plan adds:
1. `signal overlay_changed(overlay_name: String)` to NetworkManager, emitted from `set_overlay`
2. The actual shader + CanvasLayer in `tavern.gd`

The overlay intensity in this plan is binary: 1.0 when `spore_active`, 0.0 for `default`. Schritt 11 extends this to use `SporeLevel.current_level` as the multiplier.

## Files

| File | Action |
|------|--------|
| `shaders/spore_overlay.gdshader` | Create — screen-space spore effect shader |
| `scripts/network_manager.gd` | Add `overlay_changed` signal + emit in `set_overlay` |
| `scripts/tavern.gd` | Add `_setup_spore_overlay()`, call in `_ready()` |

---

## Task 1: Create the spore overlay shader

**Files:**
- Create: `shaders/spore_overlay.gdshader`

The shader samples the screen texture and applies: green tint, edge vignette, subtle time-based distortion. The `intensity` uniform drives all effects (0.0 = invisible, 1.0 = full effect).

- [ ] **Step 1: Create ruf-der-pilze/shaders/ directory and spore_overlay.gdshader**

  ```glsl
  shader_type canvas_item;

  uniform float intensity : hint_range(0.0, 1.0) = 0.0;
  uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap;

  void fragment() {
      vec2 uv = SCREEN_UV;

      // Subtle distortion — wavy lines, stronger at higher intensity
      float distort_x = sin(uv.y * 35.0 + TIME * 1.8) * 0.003 * intensity;
      float distort_y = cos(uv.x * 28.0 + TIME * 1.2) * 0.002 * intensity;

      // Sample screen with slight RGB split for chromatic aberration feel
      float split = 0.003 * intensity;
      vec4 r = texture(screen_texture, uv + vec2(distort_x + split, distort_y));
      vec4 g = texture(screen_texture, uv + vec2(distort_x,         distort_y));
      vec4 b = texture(screen_texture, uv + vec2(distort_x - split, distort_y));
      vec4 screen = vec4(r.r, g.g, b.b, 1.0);

      // Vignette — darken edges
      vec2 center = uv - 0.5;
      float vignette = 1.0 - smoothstep(0.25, 0.75, length(center) * 1.6);
      screen.rgb *= mix(1.0, vignette, intensity * 0.6);

      // Green spore tint
      vec3 spore_tint = vec3(0.05, 0.45, 0.12);
      screen.rgb = mix(screen.rgb, screen.rgb * spore_tint * 2.2, intensity * 0.35);

      // Slight brightness pulse
      float pulse = 1.0 + sin(TIME * 1.5) * 0.04 * intensity;
      screen.rgb *= pulse;

      COLOR = screen;
  }
  ```

  The shader uses `hint_screen_texture` to capture the rendered scene. The ColorRect must cover the full screen and use this shader — Godot automatically provides the screen texture.

- [ ] **Step 2: Open Godot, verify shader parses without errors**

  Open Project → any scene → check Output for shader compilation errors.

- [ ] **Step 3: Commit**

  ```bash
  git add ruf-der-pilze/shaders/spore_overlay.gdshader
  git commit -m "feat: add spore_overlay.gdshader — screen-space tint, vignette, distortion"
  ```

---

## Task 2: Add overlay_changed signal to NetworkManager

**Files:**
- Modify: `scripts/network_manager.gd` — add signal declaration + emit in `set_overlay`

- [ ] **Step 1: Add signal declaration near top of network_manager.gd**

  Add alongside the other signal declarations:
  ```gdscript
  signal overlay_changed(overlay_name: String)
  ```

- [ ] **Step 2: Emit the signal in set_overlay RPC**

  Find the `set_overlay` function (currently at end of file):
  ```gdscript
  @rpc("authority", "call_remote", "reliable")
  func set_overlay(overlay_name: String) -> void:
      GameState.set_overlay_local(my_id, overlay_name)
  ```

  Add one line:
  ```gdscript
  @rpc("authority", "call_remote", "reliable")
  func set_overlay(overlay_name: String) -> void:
      GameState.set_overlay_local(my_id, overlay_name)
      overlay_changed.emit(overlay_name)
  ```

- [ ] **Step 3: Open Godot, check for parse errors**

- [ ] **Step 4: Commit**

  ```bash
  git add ruf-der-pilze/scripts/network_manager.gd
  git commit -m "feat: network_manager — emit overlay_changed signal from set_overlay RPC"
  ```

---

## Task 3: Add spore overlay CanvasLayer to tavern.gd

**Files:**
- Modify: `scripts/tavern.gd` — add member variable, `_setup_spore_overlay()`, `_on_overlay_changed()`

The DM client already returns early in `_ready()` before reaching the player setup block, so no DM guard is needed here.

- [ ] **Step 1: Add member variable to tavern.gd**

  Near the top of the script (alongside other member variables):
  ```gdscript
  var _spore_mat: ShaderMaterial
  ```

- [ ] **Step 2: Add _setup_spore_overlay() and _on_overlay_changed() to tavern.gd**

  ```gdscript
  func _setup_spore_overlay() -> void:
      var canvas := CanvasLayer.new()
      canvas.name = "SporeOverlay"
      canvas.layer = 10  # renders above dice UI (layer 0)
      add_child(canvas)

      var rect := ColorRect.new()
      rect.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
      rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
      _spore_mat = ShaderMaterial.new()
      _spore_mat.shader = load("res://shaders/spore_overlay.gdshader") as Shader
      rect.material = _spore_mat
      canvas.add_child(rect)

      NetworkManager.overlay_changed.connect(_on_overlay_changed)


  func _on_overlay_changed(overlay_name: String) -> void:
      var intensity := 1.0 if overlay_name == "spore_active" else 0.0
      _spore_mat.set_shader_parameter("intensity", intensity)
  ```

- [ ] **Step 3: Call _setup_spore_overlay() in _ready()**

  Add at the very end of `_ready()`, after the existing `_setup_dice_ui()` call:
  ```gdscript
  _setup_spore_overlay()
  ```

  **Important:** do NOT modify any other part of `_ready()`. The DM early-return at the top already skips this. Only append — do not rewrite the method.

- [ ] **Step 4: Open Godot, check for parse errors**

- [ ] **Step 5: Run as player client**

  Log in as Spieler. Have DM toggle overlay to `spore_active` in the Regiepult.
  Expected: green tinted, slightly distorted, vignette'd screen visible to the player.
  Expected: toggling back to `default` clears the effect.

- [ ] **Step 6: Commit**

  ```bash
  git add ruf-der-pilze/scripts/tavern.gd
  git commit -m "feat: tavern — add spore overlay CanvasLayer + shader driven by overlay_changed signal"
  ```

---

## Task 4: Update STATUS.md

- [ ] **Step 1: Mark Schritt 10 ✅ in docs/STATUS.md and ruf-der-pilze/CLAUDE.md**

- [ ] **Step 2: Commit**

  ```bash
  git add docs/STATUS.md ruf-der-pilze/CLAUDE.md
  git commit -m "docs: mark Spore Overlay complete"
  ```
