# Turn Control Flow Documentation

## Overview

This document captures the turn control flow pattern that manages the alternating turns between the player and robots in this game. This pattern is **critical** for proper animation behavior with bgio-effects.

## The Core Problem

When using bgio-effects with `updateStateAfterEffects: true`, the library batches all state changes within a single "reducer tick" and holds them back until all effects in that tick complete. 

**If you naively implement turn flow**, you get:
```
Player action → Player turn ends → Robot attacks queued → Robot turn ends → Player turn starts
```

All of this happens in **one reducer tick**, so the UI doesn't update until the **entire sequence** completes. This means:
- Player damage doesn't appear until robots finish attacking
- Robot deaths don't show until player turn restarts
- Everything feels laggy and broken

## The Solution: The Criss-Cross Pattern

To break the reducer tick at the right points, we use a **criss-cross pattern** where:
- The **game layer** triggers effects that signal "turn complete"
- The **UI layer** listens for those effects and calls moves to start the next turn

This creates breaks in the reducer flow, allowing bgio-effects to release state updates between turns.

### The Flow Diagram

```
┌─────────────────────────────────────────────────────────────┐
│ PLAYER TURN (Game Layer)                                     │
│   • Player makes actions (useWeapon, useCharger, etc.)      │
│   • consumeAction() is called                                │
│   • When actionsRemaining reaches 0:                         │
│     - effects.blockPlayerInput(0)         ← Block UI         │
│     - effects.playerTurnComplete('>+.05') ← Signal complete  │
│     - events.endTurn()                    ← BGIO turn end    │
└─────────────────────────────────────────────────────────────┘
                            ↓
                    [State flush #1]
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ UI LAYER (CombatBoard.jsx)                                   │
│   • useEffectListener('playerTurnComplete', ...)            │
│   • Receives the effect                                      │
│   • Calls moves.startRobotTurn()          ← CROSS BOUNDARY   │
└─────────────────────────────────────────────────────────────┘
                            ↓
                    [State flush #2]
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ ROBOT TURN (Game Layer)                                      │
│   • startRobotTurn move executes                             │
│   • robotsAttack(G, effects) is called                       │
│     - effects.blockPlayerInput()          ← Block UI         │
│     - Queue all robot attack effects                         │
│     - effects.resumePlayerInput()         ← Schedule unblock │
│   • effects.robotTurnComplete()           ← Signal complete  │
│   • events.endTurn()                      ← BGIO turn end    │
└─────────────────────────────────────────────────────────────┘
                            ↓
                    [State flush #3]
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ UI LAYER (CombatBoard.jsx)                                   │
│   • useEffectListener('robotTurnComplete', ...)             │
│   • Receives the effect                                      │
│   • Calls moves.startPlayerTurn()         ← CROSS BOUNDARY   │
└─────────────────────────────────────────────────────────────┘
                            ↓
                    [State flush #4]
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ PLAYER TURN START (Game Layer)                               │
│   • startPlayerTurn move executes                            │
│   • effects.resumePlayerInput(0)          ← Unblock UI       │
│   • G.actionsRemaining = G.actionsPerTurn ← Restore actions  │
│   • Ready for player input!                                  │
└─────────────────────────────────────────────────────────────┘
```

## Key Implementation Details

### 1. Game Layer: Signaling Turn Complete

**File:** `src/game/CombatGame.js`

**When player turn ends:**
```javascript
function consumeAction(G, events, effects, actionCost = 1) {
  G.actionsRemaining = Math.max(0, G.actionsRemaining - actionCost);

  effects.playerActionComplete();

  if (G.actionsRemaining <= 0) {
    effects.blockPlayerInput(0);        // Immediate block
    effects.playerTurnComplete('>+.05'); // Small buffer for feel
    events.endTurn();                    // BGIO turn system
  }
}
```

**When robot turn ends:**
```javascript
startRobotTurn: ({G, events, effects}) => {
  robotsAttack(G, effects);           // Queue robot effects
  effects.robotTurnComplete();        // Signal complete
  events.endTurn();                   // BGIO turn system
},
```

### 2. UI Layer: Listening and Responding

**File:** `src/components/CombatBoard.jsx`

**Listen for player turn complete, trigger robot turn:**
```javascript
useEffectListener('playerTurnComplete', () => {
  console.log('[UI] playerTurnComplete received, calling move startRobotTurn');
  moves.startRobotTurn();
}, []);
```

**Listen for robot turn complete, trigger player turn:**
```javascript
useEffectListener('robotTurnComplete', () => {
  console.log('[UI] robotTurnComplete received, calling move startPlayerTurn');
  moves.startPlayerTurn();
}, []);
```

### 3. Game Layer: Starting Turns

**File:** `src/game/CombatGame.js`

**Start player turn (called from UI):**
```javascript
startPlayerTurn: ({G, events, effects}) => {
  console.log('[startPlayerTurn]');
  effects.resumePlayerInput(0);           // Unblock input
  G.actionsRemaining = G.actionsPerTurn;  // Restore actions
},
```

**Start robot turn (called from UI):**
```javascript
startRobotTurn: ({G, events, effects}) => {
  console.log('[startRobotTurn] robotsAttack calling robots');
  robotsAttack(G, effects);               // Execute attacks
  
  console.log('[startRobotTurn] robotsAttack restoring actionsRemaining and ending turn');
  effects.robotTurnComplete();            // Signal complete
  events.endTurn();                       // BGIO turn system
},
```

## Why This Pattern Works

### The Critical Insight

**Moving from Game → UI → Game creates separate reducer ticks.**

Each time the UI calls a move, it creates a **new reducer tick**. This means:
1. Previous effects finish and state releases
2. UI updates with current state
3. New move executes in fresh context
4. New effects queue starts

Without this pattern, everything would happen in one tick:
```javascript
// ❌ BAD: All in one tick
playerAction() {
  // Player stuff happens
  events.endTurn();
  robotsAttack();    // Queues long effects
  events.endTurn();
  // State held until ALL effects complete!
}
```

With the criss-cross pattern:
```javascript
// ✅ GOOD: Split across multiple ticks

// Tick 1: Player action completes
playerAction() {
  // Player stuff
  effects.playerTurnComplete();
  events.endTurn();
}
// → State released, UI updates

// Tick 2: Robot turn (triggered from UI)
startRobotTurn() {
  robotsAttack();  // Queues robot effects
  effects.robotTurnComplete();
  events.endTurn();
}
// → State released after robot effects

// Tick 3: Player turn (triggered from UI)
startPlayerTurn() {
  // Ready for player
}
// → State released immediately
```

## Effect Definitions

**File:** `src/game/CombatGame.js`

The turn control effects are defined in the EffectsPlugin:

```javascript
const configuredEffectsPlugin = EffectsPlugin({
  effects: {
    // Action-level completion (not strictly needed for turn control)
    playerActionComplete: {},
    robotActionComplete: {},

    // Turn-level completion (CRITICAL for turn control)
    playerTurnComplete: {},  // Signals player turn done
    robotTurnComplete: {},   // Signals robot turn done

    // Input control (for animation blocking)
    blockPlayerInput: {},    // Block UI during animations
    resumePlayerInput: {},   // Unblock UI after animations

    // Other game effects...
    damageDealt: { duration: 0.5, create: (data) => data },
    robotAttack: { 
      duration: ROBOT_ATTACK_DURATION_SECONDS, 
      create: (data) => data 
    },
  }
});
```

## Common Pitfalls and Solutions

### ❌ Pitfall 1: Calling the next turn directly

```javascript
// BAD: Don't do this!
moves: {
  useWeapon: ({G, effects, events}) => {
    // Player action
    if (G.actionsRemaining <= 0) {
      events.endTurn();
      robotsAttack(G, effects);  // Same tick!
    }
  }
}
```

**Why it fails:** Robot attacks happen in the same reducer tick, so state is held until robots finish.

**Solution:** Use the criss-cross pattern to break the tick.

### ❌ Pitfall 2: Using phases instead of effects

```javascript
// BAD: Phases alone don't work
phases: {
  playerTurn: {
    onEnd: ({G, effects}) => {
      robotsAttack(G, effects);  // Still same tick!
    }
  }
}
```

**Why it fails:** Phase transitions (`onEnd` → `onBegin`) can happen in one reducer pass.

**Solution:** The effect → UI → move pattern forces a break.

### ❌ Pitfall 3: Forgetting to block/unblock input

```javascript
// BAD: Player can click during robot attacks
robotsAttack(G, effects) {
  // Queue effects but don't block input
  effects.robotAttack({...});
}
```

**Why it fails:** Player can spam clicks during animations, breaking the game.

**Solution:** Always pair blocking with turn transitions:
```javascript
// GOOD
robotsAttack(G, effects) {
  effects.blockPlayerInput();
  // Queue effects
  effects.resumePlayerInput();
}
```

### ✅ Best Practice: Symmetric blocking

Both turn transitions should block input:
- Player turn ending: `blockPlayerInput(0)` before `playerTurnComplete`
- Robot turn running: `blockPlayerInput()` at start, `resumePlayerInput()` at end

## Debugging Turn Flow

### Console Logging

The code includes extensive logging. Expected console output:

```
[MOVE] useWeapon: blaster on target 0
[MOVE] Robot HP after effect: 0
[MOVE] About to consumeAction
[ACTION] consumeAction 1 action(s). 0 remaining.
[ACTION] consumeAction ENDING TURN - scheduling blockPlayerInput(0), playerTurnComplete(>+.05)
[MOVE] useWeapon complete
[UI-*] effect=playerTurnComplete - Actions: 0, Robots: ...
[UI] playerTurnComplete received, calling move startRobotTurn
[startRobotTurn] robotsAttack calling robots
[ROBOTS] Attacking player
[ROBOTS]   Robot 0 scheduling attack animation
[startRobotTurn] robotsAttack restoring actionsRemaining and ending turn
[UI-*] effect=robotTurnComplete - Actions: 0, Robots: ...
[UI] robotTurnComplete received, calling move startPlayerTurn
[startPlayerTurn]
```

### What to Watch For

1. **Effect order:** `playerTurnComplete` → `startRobotTurn` → `robotTurnComplete` → `startPlayerTurn`
2. **State updates:** Check `Actions: N` in the logs to see when state changes
3. **Blocked input:** UI should show "Robots attacking..." during robot turn
4. **Timing:** Small delays between turns are expected and good!

## When to Use This Pattern

### ✅ Use the criss-cross pattern when:
- You need to alternate between player and AI/system turns
- You have animations that must complete before the next turn
- You're using `updateStateAfterEffects: true`
- You need clear state boundaries between turn phases

### ❌ You might not need it when:
- Turns don't alternate (player takes multiple turns in a row)
- No animations need to complete between turns
- Using `updateStateAfterEffects: false` (but this breaks most animations)
- Real-time game without distinct turns

## Related Documentation

- **ANIMATION.md** - How animations work with bgio-effects
- **BestPractices.md** - Overall architecture and separation of concerns
- **GAME.md** - Game design and combat rules

## Summary

The criss-cross pattern is essential for proper turn flow with bgio-effects:

1. **Game layer** triggers `playerTurnComplete` / `robotTurnComplete` effects
2. **UI layer** listens for these effects
3. **UI layer** calls moves `startRobotTurn` / `startPlayerTurn`
4. **Game layer** executes the new turn

This creates breaks in the reducer tick flow, allowing state to release between turns, which makes animations feel responsive and correct.

**Remember:** The "criss-cross" is the boundary crossing between layers. Game → UI → Game. This is what forces the reducer tick breaks that make everything work.
