# Summary - 03/02/2026

## Current Task
Fix multiple CultOfQoL bugs reported by users: Knucklebones speed glitches, Mass Action softlocks, Fast Rituals visual effects, and Mass Action interaction wheel staying open.

## Goal
Fix all reported bugs while maintaining mod stability and avoiding game code replacement where possible.

---

## Bug Fixes Implemented

### Bug #1: Knucklebones Speed Graphical Glitches ✅ FIXED
**Symptoms:** Opponent's dice stay in top-right corner or vanish when speed >1.5x

**Root Causes Found:**
1. Original `ScaleDeltaTimeLoop` frame-skipping approach didn't work - each `MoveNext()` only gets one frame's deltaTime
2. DOTween animation from `RollRoutine` (1 second duration) conflicted with `GoToLocationRoutine`'s manual position setting

**Solution:**
1. Transpilers replace `Time.unscaledDeltaTime` calls with `GetScaledUnscaledDeltaTime()` which returns `Time.unscaledDeltaTime * SpeedMultiplier`
2. Added prefix to kill DOTween on dice before `GoToLocationRoutine` starts: `__instance.rectTransform.DOKill()`

**Files:** `CultOfQoL/Patches/Gameplay/KnucklebonesSpeedPatches.cs`

---

### Bug #2 & #3: Mass Reassure/Bribe Softlock ✅ FIXED (needs testing)
**Symptoms:** Lamb stuck in "Loyalty Collection" pose (floating, glowing eyes), followers disappear

**Root Cause:**
`AnyMassActionsEnabled` checked **config state** (is option enabled?) instead of **runtime state** (is mass action currently running?). Natural level-ups during mass actions had their parameters incorrectly modified.

**Solution:**
1. Added `MassActionCurrentlyRunning` and `ActiveMassRoutineCount` to track runtime state
2. Changed Prefix patches to check `MassActionCurrentlyRunning` instead of `AnyMassActionsEnabled`
3. Added 8-second safety timeout that force-resets player state if stuck

**Files:**
- `CultOfQoL/Core/Routines/RoutineTranspilers.cs` - Added runtime tracking flags
- `CultOfQoL/Patches/Followers/FollowerPatches.cs` - Set/clear flags, safety timeout

---

### Bug #4: Fast Rituals Visual Effects Persistence ✅ PREVENTATIVE FIX
**Symptoms:** Fisheye/chromatic aberration persists after resurrection ritual with fast rituals enabled

**Root Cause:** Cleanup tween may be interrupted before completing due to 10x timescale

**Solution:** Force reset chromatic aberration to default value when rituals end (marked as preventative - couldn't reproduce in testing)

**Files:** `CultOfQoL/Patches/Gameplay/RitualSermonSpeed.cs`

---

### Bug #5: Mass Action Interaction Wheel Stays Open 🔄 IN PROGRESS
**Symptoms:** After mass action, original follower's interaction wheel stays open, player can run around

**Root Cause:** Mass action is a Postfix - the original interaction with Follower A never gets explicitly closed

**Solution:**
1. Added `OriginalMassActionInteraction` field to track original interaction
2. `BeginMassAction()` now stores the original interaction
3. `DecrementMassActionCounter()` closes the original interaction when all routines complete

**Files:** `CultOfQoL/Patches/Followers/FollowerPatches.cs`

---

## Known Issue: NullReferenceException in Close()
**Stack trace shows:** `interaction_FollowerInteraction.Close()` → `BribeRoutine` → `AddAdorationIE`

**Likely Cause:** The `OnInteract` transpiler in `RoutineTranspilers.cs` uses `AnyMassActionsEnabled` (config check) at patch time, not runtime. This NOPs out `DepthOfFieldTween` calls even for normal single-follower interactions if any mass action config is enabled.

**Potential Fix:** The transpiler may need to be converted to check runtime state, or the NOP'd calls need to be made conditional somehow. This is a pre-existing issue, not caused by today's changes.

---

## Files Modified Today

| File | Changes |
|------|---------|
| `KnucklebonesSpeedPatches.cs` | Transpilers for deltaTime scaling, DOTween kill prefix |
| `RitualSermonSpeed.cs` | Visual effects reset postfix (preventative) |
| `RoutineTranspilers.cs` | Added `MassActionCurrentlyRunning`, `ActiveMassRoutineCount` |
| `FollowerPatches.cs` | Runtime tracking, safety timeout, original interaction close |

---

## Key Decisions

1. **Knucklebones:** Transpiler approach over game code replacement - scales `Time.unscaledDeltaTime` reads rather than replacing entire routines
2. **Mass Actions:** Runtime state tracking with safety timeout as fallback
3. **Fast Rituals:** Preventative fix despite not reproducing - low risk, high safety

---

## Testing Status

| Bug | Status |
|-----|--------|
| #1 Knucklebones | ✅ User confirmed fixed |
| #2/#3 Mass Softlock | ⏳ Needs testing with new build |
| #4 Fast Rituals | ✅ Preventative fix in place |
| #5 Wheel Open | ⏳ Needs testing with new build |

---

## Next Steps

1. ~~User test mass actions with new build~~
2. ~~If NullReferenceException persists, investigate `OnInteract` transpiler using config state~~
3. ~~Consider converting transpiler to runtime conditional or using different approach~~

---

## Testing Results (Session 2)

### Test Outcome: ❌ FAILED
- **Symptoms observed:**
  - Follower interaction wheels overlaying each other
  - Massive NullReferenceException log spam

### Log Analysis

```
[Error  : Unity Log] NullReferenceException: Object reference not set to an instance of an object
(wrapper dynamic-method) interaction_FollowerInteraction.DMD<interaction_FollowerInteraction::OnInteract>(...)
(wrapper dynamic-method) interaction_FollowerInteraction.DMD<interaction_FollowerInteraction::Close>(...)
interaction_FollowerInteraction.Close () (at ...)
interaction_FollowerInteraction.<BribeRoutine>b__98_1 () (at ...)
```

The NRE spam occurs for **every follower** during mass bribe, originating from `BribeRoutine`'s callback → `Close()`.

---

## Root Cause Analysis (Updated)

### Issue #1: OnInteract Transpiler Timing Bug 🔴 CRITICAL

**Location:** `RoutineTranspilers.cs` lines 62-79

**Problem:** The transpiler checks `AnyMassActionsEnabled` at **patch time (game startup)**, not runtime:
```csharp
private static IEnumerable<CodeInstruction> interaction_FollowerInteraction_OnInteract(...)
{
    var originalCodes = instructions.ToList();
    if (!AnyMassActionsEnabled) return originalCodes;  // <-- CHECKED AT PATCH TIME!
    // ... NOPs DepthOfFieldTween
}
```

**Impact:** If ANY mass action config option is enabled when the game starts, `DepthOfFieldTween` is NOP'd for ALL interactions - including normal single-follower interactions. This breaks the interaction state chain.

**Why this causes NRE:** When `Close()` is called from `BribeRoutine`'s callback on followers that went through mass-action-initiated routines (not proper `OnInteract`), some expected state is missing.

### Issue #2: Interaction Wheel Never Closed 🟡

**Location:** `FollowerPatches.cs` `BeginMassAction()` method

**Problem:** When mass action starts, the original follower's interaction wheel stays open. Each follower's routine may manipulate UI state, causing overlapping wheels.

**Code flow:**
1. Player interacts with Follower A → wheel opens
2. Selects "Bribe" → `OnFollowerCommandFinalized` Postfix triggers
3. `BeginMassAction(followers.Count, __instance)` stores the interaction but doesn't close the wheel
4. Mass routines run on all followers → potential UI conflicts
5. Wheels stack/overlap

### Issue #3: BribeRoutine Uses Callbacks, Not Coroutine Waits 🟡

**Location:** Game code `interaction_FollowerInteraction.cs` lines 3263-3285

**Problem:** `BribeRoutine` is a short coroutine that just starts animations and creates `ResourceCustomTarget` with callbacks:
```csharp
public IEnumerator BribeRoutine()
{
    // ... animation setup ...
    ResourceCustomTarget.Create(..., callback: <BribeRoutine>b__98_0, ...);
    // Coroutine ENDS HERE - callback runs later!
}
```

The callback chain: `b__98_0` → `AddAdoration()` → `b__98_1` → `Close()`

**Impact:** Our `RunEnumerator` pattern calls `DecrementMassActionCounter()` when the coroutine ends, but the actual bribe logic (including `Close()`) runs later in callbacks. This means:
- Counter decrements too early
- `MassActionCurrentlyRunning` becomes false before callbacks run
- `Close()` Prefix doesn't modify parameters correctly

---

## Proposed Fixes

### Fix #1: Remove OnInteract Transpiler
**File:** `RoutineTranspilers.cs`

Remove the entire `interaction_FollowerInteraction_OnInteract` transpiler (lines 60-79). The DepthOfField effect during mass actions is a minor visual quirk - not worth the instability it causes.

### Fix #2: Close Wheel at Mass Action Start
**File:** `FollowerPatches.cs`

In `BeginMassAction()`, immediately hide the original interaction's wheel:
```csharp
private static void BeginMassAction(int routineCount, interaction_FollowerInteraction originalInteraction)
{
    if (routineCount <= 0) return;

    // NEW: Close the original interaction's wheel immediately
    if (originalInteraction.followerInteractionWheelInstance != null)
    {
        originalInteraction.followerInteractionWheelInstance.Hide(true);
    }

    RoutinesTranspilers.ActiveMassRoutineCount = routineCount;
    // ... rest of method
}
```

### Fix #3: Don't Use RunEnumerator for Callback-Based Routines
**File:** `FollowerPatches.cs`

For routines like `BribeRoutine` that manage their own `Close()` via callbacks:
- Don't wrap in `RunEnumerator` (which expects coroutine completion = action completion)
- Just start the routine directly
- Let the routine's own callback handle cleanup

Alternative: Create a different wrapper that doesn't call `DecrementMassActionCounter()` for callback-based routines.

---

## Files to Modify

| File | Change |
|------|--------|
| `RoutineTranspilers.cs` | Remove `interaction_FollowerInteraction_OnInteract` transpiler |
| `FollowerPatches.cs` | Close wheel in `BeginMassAction()`, refactor callback-based routines |

---

## Decision Point

The mass action feature has fundamental design issues:
1. It bypasses proper `OnInteract` initialization
2. It assumes all routines are pure coroutines (they're not - some use callbacks)
3. The transpiler approach of NOP'ing calls at patch time doesn't work for conditional runtime behavior

**Options:**
- **A) Quick fix:** Remove OnInteract transpiler + close wheel immediately. May still have minor issues with callback timing.
- **B) Proper fix:** Redesign mass actions to not use the routines directly, instead applying effects manually (like how `ExtortMoneyRoutine` in FollowerPatches already does).
- **C) Disable mass bribe specifically:** It's the most problematic due to callback chains with `AddAdoration` → `LevelUpRoutine`.

---

## Session 3: Direct Effects Implementation ✅ IMPLEMENTED

### Chosen Approach: Option B - Direct Effects

Instead of running game routines on other followers, apply effects directly with animations but no callbacks.

### New File Created

**`CultOfQoL/Patches/Followers/MassActionEffects.cs`**

Contains direct effect methods for each mass action:
- `ApplyBribe()` - Animation: `Reactions/react-love2`
- `ApplyIntimidate()` - Animation: `Reactions/react-intimidate`
- `ApplyBless()` - Animation: `devotion/devotion-start`
- `ApplyRomance()` - Animation: `kiss`
- `ApplyPet()` - Animation: `pet-dog`
- `ApplyReassure()` - Animation: `Scared/scared-reassured`
- `ApplyBully()` - Animation: `Scared/scared-bullied`
- `ApplyInspire()` - Animation: `dance`
- `ApplyReeducate()` - Animation: `Reactions/react-enlightened1`
- `ApplyExtort()` - No specific animation, gold flies out

Each method:
1. Sets follower animation via `SetBodyAnimation()`
2. Plays sound effects
3. Applies stat changes
4. Calls `AddAdoration()` with **null callback** (prevents Close() issues)

Also includes helper methods with proper enum checks:
- `IsImprisoned()` - Uses `Thought` enum values, not string comparison
- `IsDissenting()` - Checks `Thought.Dissenter`
- `IsAvailable()` - Checks sleep/mating states

### Changes to FollowerPatches.cs

**Removed (no longer needed):**
- `RunEnumerator()` - Was wrapping coroutines
- `BeginMassAction()` - Was tracking counter
- `DecrementMassActionCounter()` - Was managing counter
- `ForceResetMassAction()` - Was timeout safety
- `GameManager_Update_MassActionTimeout()` - Was monitoring timeout
- `ExtortMoneyRoutine()` - Replaced by `MassActionEffects.ApplyExtort()`
- Old helper methods with string comparisons

**Kept (with comments noting may be unnecessary):**
- `OriginalMassActionInteraction` tracking
- `Close` prefix that forces `reshowMenu=false`

**Updated mass action blocks to use direct effects:**
```csharp
// Example: Mass Bribe
if (cmd == FollowerCommands.Bribe && ShouldMassBribe(followerCommands[0]))
{
    if (validFollowers.Count == 0) return;
    OriginalMassActionInteraction = __instance;
    foreach (var follower in validFollowers.Where(f =>
        FollowerCommandItems.Bribe().IsAvailable(f) &&
        MassActionEffects.IsAvailable(f.Brain) &&
        MassActionEffects.IsImprisoned(f.Brain) &&
        !MassActionEffects.IsDissenting(f.Brain)))
    {
        MassActionEffects.ApplyBribe(follower);
    }
}
```

### How It Works Now

1. **Original follower**: Game runs full routine (player animations, camera, callbacks)
2. **Other followers**: Instant direct effects with reaction animations
3. **No coroutines** = No callback timing issues
4. **No counter tracking** = Simpler code
5. `OriginalMassActionInteraction` kept as safety measure (may not be needed)

### Build Status
✅ Builds successfully

### Testing Status
⏳ Needs in-game testing

---

## Alternative Approach Under Discussion

### Prefix Nulling AddAdoration Callbacks

Instead of direct effects, could keep original coroutine approach but intercept `AddAdoration` to null callbacks:

```csharp
[HarmonyPrefix]
[HarmonyPatch(typeof(FollowerBrain), nameof(FollowerBrain.AddAdoration))]
public static void AddAdoration_Prefix(FollowerBrain __instance, ref Action callback)
{
    if (MassActionCurrentlyRunning &&
        __instance != OriginalMassActionInteraction?.follower?.Brain)
    {
        callback = null;
    }
}
```

**Pros:**
- Keeps ALL original animations, sounds, VFX
- Less code than reimplementing effects
- Single interception point

**Cons:**
- `BribeRoutine` has nested callbacks (`ResourceCustomTarget.Create` → `AddAdoration`)
- Callbacks do important things like `CompleteCurrentTask()`
- Multiple interception points needed

**Status:** Not implemented, discussed as potential alternative

---

## Continuation Instructions

### To Continue After Compact:

1. **Current state**: Direct effects approach implemented in `MassActionEffects.cs`
2. **Needs testing**: Run game, test mass bribe/bless/etc., check for:
   - NRE spam in logs
   - Interaction wheel behavior
   - Effects actually applying to followers
   - Follower animations playing
3. **If direct effects work**: Can remove `OriginalMassActionInteraction` tracking if testing confirms it's not needed
4. **If issues persist**: Consider the AddAdoration prefix approach as alternative

### Key Files:
- `CultOfQoL/Patches/Followers/MassActionEffects.cs` - NEW, direct effects
- `CultOfQoL/Patches/Followers/FollowerPatches.cs` - Simplified mass action logic
- `work_summaries/bug5-findings-2026-02-03.md` - Codex's analysis + consolidated fix plan
