import React, { useCallback, useEffect, useMemo, useState } from 'react';
import axios from 'axios';
import { MISSION_ROLL_CONTEXT_KEY, clampTarget, d100, degrees } from '../utils/diceRoller';

const COMBAT_STATE_KEY = 'dw:combat-state';

function titleForScene(scene, index) {
  return scene?.title || scene?.name || `Scene ${index + 1}`;
}

function textForScene(scene) {
  return scene?.description || scene?.briefing || scene?.summary || '';
}

function endingTextForScene(scene) {
  return scene?.endingText || scene?.ending_text || scene?.outro || scene?.completionText || '';
}

function listForScene(scene, key) {
  return Array.isArray(scene?.[key]) ? scene[key] : [];
}

function extraChallengesForScene(scene) {
  const stored = Array.isArray(scene?.extraChallenges) ? scene.extraChallenges : [];
  const source = stored.length
    ? stored
    : listForScene(scene, 'complications').map((text, index) => ({
        id: `complication-${index}`,
        text,
        rewardType: 'renown',
        rewardValue: 1,
        completed: false,
      }));

  return source
    .map((item, index) => {
      const challenge = typeof item === 'string' ? { text: item } : item || {};
      const rewardType = challenge.rewardType || challenge.reward_type || 'renown';
      const defaultValue = rewardType === 'reserve_req' ? 10 : 1;
      return {
        id: challenge.id || `extra-${index}`,
        text: challenge.text || challenge.name || challenge.description || `Extra challenge ${index + 1}`,
        rewardType,
        rewardValue: Number(challenge.rewardValue ?? challenge.reward_value ?? defaultValue) || defaultValue,
        completed: Boolean(challenge.completed),
      };
    })
    .filter(challenge => challenge.text);
}

function rewardLabel(challenge) {
  if (!challenge) return '';
  if (challenge.rewardType === 'reserve_req') return `+${challenge.rewardValue} Reserve Req`;
  if (challenge.rewardType === 'xp') return `+${challenge.rewardValue} XP`;
  return `+${challenge.rewardValue} Renown`;
}

function isCombatScene(scene) {
  const type = String(scene?.type || '').toLowerCase();
  return type.includes('combat') || type.includes('boss') || Boolean(scene?.combat);
}

function defaultChecksForScene(scene) {
  const type = String(scene?.type || '').toLowerCase();
  const squadTacticsCheck = {
    name: 'Squad Tactics',
    skill: 'Tactics',
    characteristic: 'Int',
    target: 45,
    modifier: 0,
    rulesNote: 'Core: Tactics is an Advanced Intelligence Skill. Use it to read battlefield doctrine, manoeuvres, ambushes, deployments, or the best approach. It does not grant free Squad Mode abilities or Cohesion.',
    reward: 'On success, the team identifies the best tactical approach for this scene; extra Degrees of Success may reveal a better position, priority threat, or safer route.',
  };
  if (type.includes('combat')) {
    return [
      squadTacticsCheck,
      { name: 'Identify priority target', skill: 'Awareness', characteristic: 'Per', target: 45, modifier: 0, reward: 'Learn the most dangerous enemy or ambush vector.' },
      { name: 'Hold firing position', skill: 'Dodge', characteristic: 'Ag', target: 45, modifier: -10, reward: 'Avoid being driven out of cover this round.' },
    ];
  }
  if (type.includes('investigation') || type.includes('puzzle')) {
    return [
      squadTacticsCheck,
      { name: 'Read the spoor and auspex ghosts', skill: 'Awareness', characteristic: 'Per', target: 45, modifier: 0, reward: 'Reveal the next clue or hidden movement.' },
      { name: 'Decode corrupted systems', skill: 'Tech-Use', characteristic: 'Int', target: 45, modifier: -10, reward: 'Recover data before the trail degrades.' },
    ];
  }
  if (type.includes('social')) {
    return [
      squadTacticsCheck,
      { name: 'Stabilise frightened survivors', skill: 'Command', characteristic: 'Fel', target: 45, modifier: 0, reward: 'Gain cooperation and a useful witness detail.' },
      { name: 'Interrogate conflicting accounts', skill: 'Scrutiny', characteristic: 'Per', target: 45, modifier: -10, reward: 'Separate panic from truth.' },
    ];
  }
  return [
    squadTacticsCheck,
    { name: 'Maintain tactical awareness', skill: 'Awareness', characteristic: 'Per', target: 45, modifier: 0, reward: 'Spot the next useful detail.' },
    { name: 'Coordinate the kill-team', skill: 'Command', characteristic: 'Fel', target: 45, modifier: 0, reward: 'Grant a clear tactical direction.' },
  ];
}

const STANDARD_ACTIONS = [
  { name: 'Scan for threats', skill: 'Awareness', characteristic: 'Per', target: 45, modifier: 0, reward: 'Spot hidden enemies, traps, movement, or useful battlefield detail.' },
  { name: 'Read tactical situation', skill: 'Tactics', characteristic: 'Int', target: 45, modifier: 0, rulesNote: 'Use Tactics to assess doctrine, priority targets, ambush risk, or best approach. This is not a free Squad Mode ability.', reward: 'Identify a tactical edge or priority threat.' },
  { name: 'Coordinate squad action', skill: 'Command', characteristic: 'Fel', target: 45, modifier: 0, reward: 'Give the kill-team a clear direction or steady an ally under pressure.' },
  { name: 'Operate or bypass machine', skill: 'Tech-Use', characteristic: 'Int', target: 45, modifier: 0, reward: 'Open, disable, decode, stabilize, or exploit a machine system.' },
  { name: 'Diagnose biological threat', skill: 'Medicae', characteristic: 'Int', target: 45, modifier: 0, reward: 'Identify injury, mutation, toxin, infection, or usable biological data.' },
  { name: 'Scrutinise motive or lie', skill: 'Scrutiny', characteristic: 'Per', target: 45, modifier: 0, reward: 'Read fear, deception, corruption, or hidden intent.' },
  { name: 'Force open or hold position', skill: 'Strength', characteristic: 'S', target: 45, modifier: 0, reward: 'Move an obstacle, hold a door, brace a collapse, or force a path.' },
  { name: 'Move under pressure', skill: 'Agility', characteristic: 'Ag', target: 45, modifier: 0, reward: 'Cross a gap, dodge debris, move fast, or reach cover.' },
  { name: 'Set or disarm charges', skill: 'Demolition', characteristic: 'Int', target: 45, modifier: 0, reward: 'Place explosives, disarm charges, or identify weak structural points.' },
  { name: 'Resist fear or warp pressure', skill: 'Willpower', characteristic: 'Wp', target: 45, modifier: 0, reward: 'Hold discipline against fear, psychic pressure, or corruption.' },
];

function combatForScene(scene) {
  if (scene?.combat && typeof scene.combat === 'object') return scene.combat;
  const enemies = sceneEnemies(scene);
  return {
    enemy: enemies[0] || 'Custom/None',
    weapon: '',
    bs: 45,
    ws: 45,
    modifier: String(scene?.type || '').toLowerCase().includes('combat') ? 0 : -10,
    damage: '1d10+5',
    targetTB: 4,
    targetArmour: 5,
    mode: 'single',
    rof: 1,
  };
}

function missionTheme(mission) {
  return mission?.theme ? String(mission.theme).replace(/_/g, ' ') : 'Unassigned';
}

function sceneEnemies(scene) {
  if (!Array.isArray(scene?.enemies)) return [];
  return scene.enemies.map(enemy => enemy.name || enemy.type || 'Unknown enemy');
}

function sceneEnemyObjects(scene) {
  if (!Array.isArray(scene?.enemies)) return [];
  return scene.enemies
    .map(enemy => typeof enemy === 'string' ? { name: enemy } : enemy)
    .filter(enemy => enemy?.name || enemy?.type)
    .map(enemy => ({ ...enemy, name: enemy.name || enemy.type }));
}

function initiativeKey(entry) {
  if (!entry || typeof entry !== 'object') return '';
  const name = String(entry.player || entry.name || 'unknown').trim().toLowerCase();
  const type = String(entry.type || 'player').toLowerCase();
  if (type !== 'npc') return `player:${name}`;
  const stamp = entry.ts ?? entry.createdAt ?? entry.created_at ?? entry.dieRoll ?? entry.total ?? '';
  return `npc:${name}:${stamp}`;
}

function sortInitiatives(entries) {
  return [...(Array.isArray(entries) ? entries : [])].sort((a, b) => {
    const aTotal = Number(a?.total ?? 0);
    const bTotal = Number(b?.total ?? 0);
    if (bTotal !== aTotal) return bTotal - aTotal;

    const aDie = Number(a?.dieRoll ?? 0);
    const bDie = Number(b?.dieRoll ?? 0);
    if (bDie !== aDie) return bDie - aDie;

    const aTs = Number(a?.ts ?? a?.createdAt ?? a?.created_at ?? 0);
    const bTs = Number(b?.ts ?? b?.createdAt ?? b?.created_at ?? 0);
    if (aTs !== bTs) return aTs - bTs;

    return String(a?.player || '').localeCompare(String(b?.player || ''));
  });
}

function turnLabel(entry) {
  return String(entry?.player || entry?.name || 'Unknown').trim() || 'Unknown';
}

const CHARACTERISTIC_KEYS = {
  WS: ['WS', 'Ws', 'ws'],
  BS: ['BS', 'Bs', 'bs'],
  S: ['S', 's'],
  T: ['T', 't'],
  Ag: ['Ag', 'AG', 'ag'],
  Int: ['Int', 'INT', 'int'],
  Per: ['Per', 'PER', 'per'],
  WP: ['WP', 'Wp', 'wp'],
  Wp: ['WP', 'Wp', 'wp'],
  Fel: ['Fel', 'FEL', 'fel'],
};

function characteristicValue(chars, key, fallback = 45) {
  const keys = CHARACTERISTIC_KEYS[key] || [key];
  for (const k of keys) {
    if (typeof chars?.[k] === 'number') return chars[k];
  }
  return fallback;
}

function skillTrainingBonus(skill) {
  if (skill?.plus20) return 20;
  if (skill?.plus10) return 10;
  if (skill?.trained) return 0;
  return -20;
}

function skillTrainingLabel(skill) {
  if (skill?.plus20) return '+20 trained';
  if (skill?.plus10) return '+10 trained';
  if (skill?.trained) return '+0 trained';
  return '-20 untrained';
}

function matchingSheetSkill(skills, check) {
  if (!skills || !check) return null;
  const wanted = String(check.skill || check.name || '').toLowerCase();
  return Object.entries(skills).find(([name]) => {
    const clean = name.toLowerCase().replace(/\s*\([^)]*\)/g, '');
    return clean === wanted || clean.includes(wanted) || wanted.includes(clean);
  })?.[1] || null;
}

function woundsLabel(wounds) {
  if (typeof wounds === 'number') return `${wounds} W`;
  if (!wounds || typeof wounds !== 'object') return 'W ?';
  const current = wounds.current ?? wounds.cur ?? wounds.total ?? wounds.max ?? '?';
  const total = wounds.total ?? wounds.max ?? current;
  const fatigue = wounds.fatigue ? `, Fatigue ${wounds.fatigue}` : '';
  return `${current}/${total} W${fatigue}`;
}

function rollSummary(roll) {
  if (!roll) return '';
  const payload = roll.payload || {};
  if (roll.roll_type === 'check') {
    return `Last: ${roll.player_name || 'unknown'} rolled ${payload.roll ?? '?'} vs ${payload.target ?? '?'} - ${payload.success ? `${payload.dos || 0} DoS` : `${payload.dof || 0} DoF`}`;
  }
  if (roll.roll_type === 'attack') {
    const damage = payload.success
      ? `, damage ${payload.appliedDamage ?? 0} (${payload.woundsBefore ?? '?'} -> ${payload.woundsAfter ?? '?'})`
      : '';
    const dead = payload.defeated ? ' DEAD' : '';
    return `Last: ${roll.player_name || payload.attacker || 'unknown'} attack ${payload.attackRoll ?? '?'} vs ${payload.target ?? '?'} - ${payload.success ? 'Hit' : 'Miss'}${damage}${dead}${payload.enemy ? ` (${payload.enemy})` : ''}`;
  }
  return '';
}

function parseDamageExpression(value) {
  const text = String(value || '');
  const match = text.match(/(\d+)d(\d+)(?:\s*([+-])\s*(\d+))?/i);
  if (!match) return { count: 1, faces: 10, flat: 0, expression: '1d10' };
  const count = Math.max(1, parseInt(match[1], 10) || 1);
  const faces = Math.max(2, parseInt(match[2], 10) || 10);
  const sign = match[3] === '-' ? -1 : 1;
  const flat = match[4] ? sign * (parseInt(match[4], 10) || 0) : 0;
  return { count, faces, flat, expression: match[0].replace(/\s+/g, '') };
}

function rollDamageExpression(value) {
  const parsed = parseDamageExpression(value);
  const rolls = Array.from({ length: parsed.count }, () => Math.floor(Math.random() * parsed.faces) + 1);
  return {
    ...parsed,
    rolls,
    total: rolls.reduce((sum, roll) => sum + roll, 0) + parsed.flat,
  };
}

function firstWeaponDamage(tab, attackSkill) {
  const weapons = Array.isArray(tab?.weapons) ? tab.weapons : [];
  const wantedMelee = attackSkill === 'WS';
  const weapon = weapons.find(item => {
    const cls = String(item.class || item.type || '').toLowerCase();
    return item.damage && (wantedMelee ? cls.includes('melee') : !cls.includes('melee'));
  }) || weapons.find(item => item.damage);
  return {
    name: weapon?.name || 'Default attack',
    damage: weapon?.damage || (wantedMelee ? '1d10+5' : '2d10+5'),
    pen: Number(weapon?.pen || 0) || 0,
  };
}

function normalizeMission(mission) {
  if (!mission) return null;
  const scenes = Array.isArray(mission.scenes) ? mission.scenes : [];
  const currentScene = Math.max(0, Math.min(Number(mission.current_scene || 0), Math.max(scenes.length - 1, 0)));
  return { ...mission, scenes, current_scene: currentScene };
}

const CONDITIONS = ['Pinned', 'Stunned', 'Bleeding', 'Grappled', 'Knocked Down', 'Fatigued'];

const FEAR_RATINGS = [
  { value: 0, label: 'None', penaltyValue: 0 },
  { value: 1, label: 'Disturbing (1)', penalty: '+0 to Fear test', penaltyValue: 0 },
  { value: 2, label: 'Frightening (2)', penalty: '–10 to Fear test', penaltyValue: -10 },
  { value: 3, label: 'Horrifying (3)', penalty: '–20 to Fear test', penaltyValue: -20 },
  { value: 4, label: 'Terrifying (4)', penalty: '–30 to Fear test', penaltyValue: -30 },
];

const ACTION_ECONOMY = [
  { type: 'Half Action', color: 'text-blue-300', border: 'border-blue-800', bg: 'bg-blue-950/30', examples: 'Standard Attack · Move · Aim · Ready · Feint · Stand/Mount' },
  { type: 'Full Action', color: 'text-amber-300', border: 'border-amber-800', bg: 'bg-amber-950/30', examples: 'Charge · All Out Attack · Run · Disengage · Grapple · Overwatch' },
  { type: 'Reaction', color: 'text-emerald-300', border: 'border-emerald-800', bg: 'bg-emerald-950/30', examples: 'Dodge · Parry  (once per round, only when not your turn)' },
  { type: 'Free Action', color: 'text-slate-400', border: 'border-slate-700', bg: 'bg-slate-900/30', examples: 'Speak briefly · Drop item · Activate auto-senses' },
];

const SKILL_DIFFICULTIES = [
  { label: 'Trivial', value: 60 },
  { label: 'Elementary', value: 50 },
  { label: 'Simple', value: 40 },
  { label: 'Easy', value: 30 },
  { label: 'Routine', value: 20 },
  { label: 'Ordinary', value: 10 },
  { label: 'Challenging', value: 0 },
  { label: 'Difficult', value: -10 },
  { label: 'Hard', value: -20 },
  { label: 'Very Hard', value: -30 },
];

const SKILL_SITUATION_MODIFIERS = [
  { id: 'assistance', label: 'Assistance', value: 10 },
  { id: 'good_tools', label: 'Good tools/auspex', value: 10 },
  { id: 'extra_time', label: 'Extra time', value: 10 },
  { id: 'poor_tools', label: 'Poor/no tools', value: -10 },
  { id: 'rushed', label: 'Rushed', value: -10 },
  { id: 'hostile_environment', label: 'Hostile environment', value: -10 },
  { id: 'damaged_system', label: 'Damaged/unstable system', value: -10 },
  { id: 'severe_interference', label: 'Severe interference', value: -20 },
];

// ─── Shared roll feed row ────────────────────────────────────────────────────
function RollFeedRow({ roll, showDelete, onDelete }) {
  const payload = roll.payload || {};
  const success = payload.success;
  return (
    <div className="rounded border border-slate-700 bg-slate-950/40 p-3 text-sm">
      <div className="flex flex-wrap items-center justify-between gap-2">
        <span className="font-semibold capitalize">
          {roll.player_name || 'unknown'} · {roll.roll_type} · {roll.label || payload.name || 'Roll'}
        </span>
        <div className="flex items-center gap-2">
          <span className="text-slate-400 text-xs">{roll.created_at ? new Date(roll.created_at).toLocaleTimeString('da-DK') : ''}</span>
          {showDelete && (
            <button onClick={() => onDelete(roll.id)} className="rounded bg-slate-800 px-2 py-1 text-xs text-slate-300 hover:bg-slate-700">
              Delete
            </button>
          )}
        </div>
      </div>
      <div className="mt-1 text-slate-300">
        {roll.roll_type === 'decision' && payload.declaration && (
          <span className="italic text-blue-200">"{payload.declaration}"</span>
        )}
        {typeof payload.roll !== 'undefined' && (
          <>
            Roll <strong>{payload.roll}</strong> vs {payload.target}{' '}
            {success
              ? <span className="text-emerald-300">Success {payload.dos ? `(${payload.dos} DoS)` : ''}</span>
              : <span className="text-rose-300">Fail {payload.dof ? `(${payload.dof} DoF)` : ''}</span>}
          </>
        )}
        {typeof payload.attackRoll !== 'undefined' && (
          <>Attack <strong>{payload.attackRoll}</strong> vs {payload.target} · Hits {payload.hits || 0}{payload.where ? ` · ${payload.where}` : ''}</>
        )}
        {typeof payload.total !== 'undefined' && roll.roll_type === 'initiative' && (
          <span>{`Initiative ${payload.total} (d10:${payload.dieRoll} + Ab:${payload.agBonus})`}</span>
        )}
      </div>
    </div>
  );
}

export default function MissionTab({ authedPlayer }) {
  const isGM = authedPlayer === 'gm';

  // Core state
  const [missions, setMissions] = useState([]);
  const [activeMission, setActiveMission] = useState(null);
  const [selectedMissionId, setSelectedMissionId] = useState('');
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [message, setMessage] = useState('');
  const [error, setError] = useState('');

  // Roll tracking
  const [rollResults, setRollResults] = useState([]);
  const [rollFeed, setRollFeed] = useState([]);
  const [rolledChecks, setRolledChecks] = useState(new Set());
  const [fateUsedChecks, setFateUsedChecks] = useState(new Set()); // #9 fate point

  // Combat UI state
  const [agBonus, setAgBonus] = useState('');
  const [npcName, setNpcName] = useState('');   // #3 NPC initiative
  const [npcRoll, setNpcRoll] = useState('');
  const [wpTarget, setWpTarget] = useState('60'); // #7 fear test
  const [showActionRef, setShowActionRef] = useState(false);
  const [declarationText, setDeclarationText] = useState(''); // #8 decision checks

  // Scene Secrets UI — check options expand (#6)
  const [checkOpenOptions, setCheckOpenOptions] = useState(null); // { name, assignedTo, isDecision }
  const [playerList, setPlayerList] = useState([]);

  // Mission completion (#11)
  const [showMissionReport, setShowMissionReport] = useState(false);
  const [outcomeNotes, setOutcomeNotes] = useState('');

  // Scene editor
  const [editingScene, setEditingScene] = useState(false);
  const [sceneEdit, setSceneEdit] = useState(null);

  // GM manual roll entry
  const [gmManualRoll, setGmManualRoll] = useState(null); // { checkName, playerName, value }
  const [playerSheets, setPlayerSheets] = useState({});
  const [miniRollerState, setMiniRollerState] = useState({});

  // ─── Data loading ───────────────────────────────────────────────

  const loadMissions = useCallback(async function loadMissions({ silent = false } = {}) {
    if (!silent) { setLoading(true); setError(''); }
    try {
      if (isGM) {
        const [allRes, activeRes] = await Promise.all([
          axios.get('/api/missions'),
          axios.get('/api/missions/active/current'),
        ]);
        const all = Array.isArray(allRes.data) ? allRes.data.map(normalizeMission) : [];
        const active = normalizeMission(activeRes.data);
        setMissions(prev => JSON.stringify(prev) !== JSON.stringify(all) ? all : prev);
        setActiveMission(prev => JSON.stringify(prev) !== JSON.stringify(active) ? active : prev);
        if (!silent) setSelectedMissionId(active?.id ? String(active.id) : (all[0]?.id ? String(all[0].id) : ''));
      } else {
        const activeRes = await axios.get('/api/missions/active/player');
        const active = normalizeMission(activeRes.data);
        setActiveMission(prev => JSON.stringify(prev) !== JSON.stringify(active) ? active : prev);
      }
    } catch (e) {
      if (!silent) setError(`Failed to load missions: ${e.response?.data?.error || e.message}`);
    } finally {
      if (!silent) setLoading(false);
    }
  }, [isGM]);

  // #1 — roll feed visible to all (no isGM guard)
  const loadRollFeed = useCallback(async function loadRollFeed() {
    try {
      const res = await axios.get('/api/missions/active/rolls/feed?limit=200');
      const next = Array.isArray(res.data) ? res.data : [];
      setRollFeed(prev => JSON.stringify(prev) !== JSON.stringify(next) ? next : prev);
    } catch (e) {
      // non-blocking
    }
  }, []);

  useEffect(() => { loadMissions(); }, [loadMissions]);

  // #6 — load player names for check assignment
  useEffect(() => {
    axios.get('/api/players').then(res => {
      if (Array.isArray(res.data)) {
        const names = res.data.map(p => p.name).filter(n => n && n !== 'gm');
        setPlayerList(names);
        Promise.all(names.map(name =>
          axios.get(`/api/players/${name}`).then(playerRes => [name, playerRes.data]).catch(() => [name, null])
        )).then(entries => {
          const sheets = {};
          entries.forEach(([name, player]) => {
            if (player) sheets[name] = player;
          });
          setPlayerSheets(sheets);
        });
      }
    }).catch(() => {});
  }, []);

  // GM polls roll feed every 5s
  useEffect(() => {
    loadRollFeed();
    const timer = setInterval(loadRollFeed, 5000);
    return () => clearInterval(timer);
  }, [loadRollFeed]);

  // #10 — players poll mission + roll feed every 4s (halved from 8s)
  useEffect(() => {
    if (isGM) return undefined;
    const timer = setInterval(() => {
      loadMissions({ silent: true });
      loadRollFeed();
    }, 4000);
    return () => clearInterval(timer);
  }, [isGM, loadMissions, loadRollFeed]);

  // ─── Derived scene state ─────────────────────────────────────────

  const currentScene = useMemo(() => {
    if (!activeMission?.scenes?.length) return null;
    return activeMission.scenes[activeMission.current_scene] || activeMission.scenes[0];
  }, [activeMission]);

  const activeSceneIndex = activeMission?.active_scene_index ?? activeMission?.current_scene ?? 0;

  const currentSceneIndex = activeMission?.current_scene ?? 0;
  useEffect(() => {
    const sessionState = currentScene?.sessionState || {};
    const playerKey = authedPlayer || 'unknown';
    const playerRolled = sessionState.rolledChecksByPlayer?.[playerKey];
    const playerFate = sessionState.fateUsedChecksByPlayer?.[playerKey];
    const playerResults = sessionState.rollResultsByPlayer?.[playerKey];
    setRolledChecks(new Set(Array.isArray(playerRolled) ? playerRolled : (Array.isArray(sessionState.rolledChecks) ? sessionState.rolledChecks : [])));
    setFateUsedChecks(new Set(Array.isArray(playerFate) ? playerFate : (Array.isArray(sessionState.fateUsedChecks) ? sessionState.fateUsedChecks : [])));
    setRollResults(Array.isArray(playerResults) ? playerResults.slice(0, 5) : (Array.isArray(sessionState.rollResults) ? sessionState.rollResults.slice(0, 5) : []));
    setDeclarationText(sessionState.declarationText || '');
    setWpTarget(String(sessionState.wpTarget || '60'));
    if (sessionState.miniRollerState && typeof sessionState.miniRollerState === 'object') {
      setMiniRollerState(prev => ({ ...prev, [activeSceneIndex]: sessionState.miniRollerState }));
    }
  }, [activeMission?.id, currentSceneIndex, activeSceneIndex, currentScene, authedPlayer]);

  // sync outcome notes when a different mission loads (keyed on id, not scenes array)
  useEffect(() => {
    if (!activeMission?.scenes?.length) return;
    const lastScene = activeMission.scenes[activeMission.scenes.length - 1];
    setOutcomeNotes(lastScene?.outcome || '');
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [activeMission?.id]);

  const sceneChecks = useMemo(() => {
    if (!currentScene) return [];
    return listForScene(currentScene, 'checks').length ? listForScene(currentScene, 'checks') : defaultChecksForScene(currentScene);
  }, [currentScene]);

  const sceneObjectives = useMemo(() => listForScene(currentScene, 'objectives'), [currentScene]);
  const sceneComplications = useMemo(() => listForScene(currentScene, 'complications'), [currentScene]);
  const sceneExtraChallenges = useMemo(() => extraChallengesForScene(currentScene), [currentScene]);
  const sceneCombat = useMemo(() => combatForScene(currentScene), [currentScene]);
  const playPrompt = currentScene?.playPrompt || null;
  const combatActive = playPrompt?.type === 'combat';

  // #2 — per-element reveal flags
  // New scenes use scene.reveal.{title,mood,description,objectives,complications}
  // Old scenes with revealed:true are treated as fully revealed for backwards compat
  const sceneReveal = useMemo(() => {
    const legacy = currentScene?.revealed === true; // old all-or-nothing flag
    const r = currentScene?.reveal || {};
    return {
      title:         r.title         ?? legacy,
      mood:          r.mood          ?? legacy,
      description:   r.description   ?? legacy,
      objectives:    r.objectives    ?? legacy,
      complications: r.complications ?? false,   // never auto-revealed
      checks: r.checks || {},
      combat:        r.combat        ?? false,
    };
  }, [currentScene]);

  const combatState = useMemo(() => {
    const cs = currentScene?.combatState || {};
    return {
      round: cs.round ?? 1,
      initiatives: Array.isArray(cs.initiatives) ? cs.initiatives : [],
      conditions: (cs.conditions && typeof cs.conditions === 'object') ? cs.conditions : {},
      fearRating: cs.fearRating ?? 0,
      turnKey: typeof cs.turnKey === 'string' ? cs.turnKey : '',
      turnUpdatedAt: cs.turnUpdatedAt || '',
    };
  }, [currentScene]);

  const sortedCombatInitiatives = useMemo(
    () => sortInitiatives(combatState.initiatives),
    [combatState.initiatives]
  );

  const combatTurnIndex = useMemo(() => {
    if (!sortedCombatInitiatives.length) return -1;
    const found = sortedCombatInitiatives.findIndex(entry => initiativeKey(entry) === combatState.turnKey);
    return found >= 0 ? found : 0;
  }, [sortedCombatInitiatives, combatState.turnKey]);

  const combatCurrentTurn = combatTurnIndex >= 0 ? sortedCombatInitiatives[combatTurnIndex] || null : null;
  const combatNextTurn = combatTurnIndex >= 0 && sortedCombatInitiatives.length > 1
    ? sortedCombatInitiatives[(combatTurnIndex + 1) % sortedCombatInitiatives.length]
    : null;

  const myInitiative = combatState.initiatives.find(e => e.player === authedPlayer);

  // #3 — entities include NPC initiative entries
  const combatEntities = useMemo(() => {
    const fromInit = combatState.initiatives.map(e => ({
      name: e.player,
      type: e.type === 'npc' ? 'npc' : 'player',
    }));
    const initNames = new Set(fromInit.map(e => e.name));
    const enemies = sceneEnemies(currentScene)
      .filter(name => !initNames.has(name))
      .map(name => ({ name, type: 'enemy' }));
    return [...fromInit, ...enemies];
  }, [combatState.initiatives, currentScene]);

  // #11 — mission complete
  const missionComplete = useMemo(() =>
    activeMission?.scenes?.length > 0 && activeMission.scenes.every(s => s.completed),
    [activeMission]);

  const earnedMissionRewards = useMemo(() => {
    const totals = { renown: 0, reserveReq: 0, xp: 0, completed: 0, total: 0 };
    if (!activeMission?.scenes?.length) return totals;
    activeMission.scenes.forEach(scene => {
      extraChallengesForScene(scene).forEach(challenge => {
        totals.total += 1;
        if (!challenge.completed) return;
        totals.completed += 1;
        if (challenge.rewardType === 'reserve_req') totals.reserveReq += challenge.rewardValue;
        else if (challenge.rewardType === 'xp') totals.xp += challenge.rewardValue;
        else totals.renown += challenge.rewardValue;
      });
    });
    return totals;
  }, [activeMission]);

  const missionXpEstimate = useMemo(() => {
    const checkSuccesses = rollFeed.filter(roll => roll.roll_type === 'check' && roll.payload?.success).length;
    const checkXp = checkSuccesses * 50;
    const sceneXp = (activeMission?.scenes || []).reduce((sum, scene) => {
      if (!scene.completed || isCombatScene(scene)) return sum;
      return sum + 100;
    }, 0);
    const combatXp = (activeMission?.scenes || []).reduce((sum, scene, index) => {
      if (!isCombatScene(scene)) return sum;
      if (scene.completed) return sum + 200;
      const hasCombatRolls = rollFeed.some(roll =>
        roll.scene_index === index && ['attack', 'damage', 'initiative', 'fear'].includes(roll.roll_type)
      );
      return hasCombatRolls ? sum + 75 : sum;
    }, 0);
    return {
      checkXp,
      sceneXp,
      combatXp,
      bonusXp: earnedMissionRewards.xp,
      total: checkXp + sceneXp + combatXp + earnedMissionRewards.xp,
      checkSuccesses,
    };
  }, [activeMission, rollFeed, earnedMissionRewards.xp]);

  // #1 — roll feed filtered for player view (current scene only)
  const playerRollFeed = useMemo(() => {
    if (isGM) return rollFeed;
    return rollFeed.filter(r => r.scene_index === activeSceneIndex);
  }, [isGM, rollFeed, activeSceneIndex]);

  // ─── Roll feed stats for mission report (#11) ────────────────────
  const rollStats = useMemo(() => {
    const checks = rollFeed.filter(r => r.roll_type === 'check');
    const successes = checks.filter(r => r.payload?.success);
    return { total: checks.length, successes: successes.length };
  }, [rollFeed]);

  // ─── Combat state → localStorage sync (#4) ──────────────────────
  useEffect(() => {
    try {
      const state = {
        round: combatState.round,
        fearRating: combatState.fearRating,
        active: combatActive,
        conditions: combatState.conditions,
        turnKey: combatState.turnKey || null,
        initiativeOrder: sortedCombatInitiatives.map(entry => ({
          name: turnLabel(entry),
          total: entry.total,
          type: entry.type || 'player',
          key: initiativeKey(entry),
        })),
        currentTurn: combatCurrentTurn ? {
          name: turnLabel(combatCurrentTurn),
          total: combatCurrentTurn.total,
          type: combatCurrentTurn.type || 'player',
          key: initiativeKey(combatCurrentTurn),
        } : null,
        nextTurn: combatNextTurn ? {
          name: turnLabel(combatNextTurn),
          total: combatNextTurn.total,
          type: combatNextTurn.type || 'player',
          key: initiativeKey(combatNextTurn),
        } : null,
      };
      window.localStorage.setItem(COMBAT_STATE_KEY, JSON.stringify(state));
      window.dispatchEvent(new CustomEvent('dw:combat-state', { detail: state }));
    } catch {}
  }, [
    combatState.round,
    combatState.fearRating,
    combatActive,
    combatState.conditions,
    combatState.turnKey,
    sortedCombatInitiatives,
    combatCurrentTurn,
    combatNextTurn,
  ]);

  // ─── Server mutations ────────────────────────────────────────────

  async function recordMissionRoll(rollType, label, payload) {
    try {
      await axios.post('/api/missions/rolls', {
        missionId: activeMission?.id,
        playerName: authedPlayer,
        rollType,
        sceneIndex: activeSceneIndex,
        label,
        payload,
      });
      await loadRollFeed();
    } catch (e) {
      // non-blocking
    }
  }

  async function recordMissionRollForScene(sceneIndex, playerName, rollType, label, payload) {
    try {
      await axios.post('/api/missions/rolls', {
        missionId: activeMission?.id,
        playerName: playerName || authedPlayer,
        rollType,
        sceneIndex,
        label,
        payload,
      });
      await loadRollFeed();
    } catch (e) {
      // non-blocking
    }
  }

  async function deleteRoll(id) {
    try {
      await axios.delete(`/api/missions/rolls/${id}`);
      await loadRollFeed();
    } catch (e) {
      setError(`Failed to delete roll: ${e.response?.data?.error || e.message}`);
    }
  }

  async function clearRollFeed() {
    try {
      await axios.delete('/api/missions/rolls');
      setRollFeed([]);
      setMessage('Roll feed cleared.');
    } catch (e) {
      setError(`Failed to clear roll feed: ${e.response?.data?.error || e.message}`);
    }
  }

  async function saveProgress(nextSceneIndex, nextScenes = activeMission?.scenes || []) {
    if (!activeMission) return;
    const boundedScene = Math.max(0, Math.min(nextSceneIndex, Math.max(nextScenes.length - 1, 0)));
    const optimistic = normalizeMission({ ...activeMission, current_scene: boundedScene, scenes: nextScenes });
    setActiveMission(optimistic);
    setSaving(true);
    setError('');
    try {
      const res = await axios.put(`/api/missions/${activeMission.id}/progress`, {
        currentScene: boundedScene,
        scenes: nextScenes,
      });
      setActiveMission(normalizeMission(res.data?.mission));
      setMessage('Mission progress saved.');
    } catch (e) {
      setError(`Failed to save progress: ${e.response?.data?.error || e.message}`);
      await loadMissions();
    } finally {
      setSaving(false);
    }
  }

  async function patchScene(updates) {
    if (!activeMission?.scenes?.length) return;
    const index = activeMission.current_scene;
    const nextScenes = activeMission.scenes.map((scene, i) =>
      i === index ? { ...scene, ...updates } : scene
    );
    await saveProgress(index, nextScenes);
    await loadMissions({ silent: true });
  }

  async function patchSceneAt(sceneIndex, updates) {
    if (!activeMission?.scenes?.length) return;
    if (!isGM) {
      try {
        const res = await axios.put('/api/missions/active/scene', { patch: updates });
        const mission = normalizeMission(res.data?.mission);
        if (mission) {
          const current = mission.scenes?.[mission.current_scene];
          setActiveMission({
            ...activeMission,
            scenes: current ? [current] : activeMission.scenes,
            current_scene: 0,
            active_scene_index: mission.current_scene,
          });
        }
        await loadMissions({ silent: true });
      } catch (e) {
        setError(`Failed to update active scene: ${e.response?.data?.error || e.message}`);
      }
      return;
    }
    const nextScenes = activeMission.scenes.map((scene, i) =>
      i === sceneIndex ? { ...scene, ...updates } : scene
    );
    await saveProgress(activeMission.current_scene, nextScenes);
    await loadMissions({ silent: true });
  }

  async function patchActiveSceneSessionState(updates, sceneIndex = activeSceneIndex, scene = currentScene) {
    if (!scene) return;
    const sessionState = {
      ...(scene.sessionState || {}),
      ...updates,
      updatedAt: new Date().toISOString(),
    };
    await patchSceneAt(sceneIndex, { sessionState });
  }

  function playerSessionStatePatch({
    rolled = rolledChecks,
    fate = fateUsedChecks,
    results = rollResults,
    declaration = declarationText,
    wp = wpTarget,
    extra = {},
  } = {}) {
    const sessionState = currentScene?.sessionState || {};
    const playerKey = authedPlayer || 'unknown';
    return {
      ...extra,
      rolledChecksByPlayer: {
        ...(sessionState.rolledChecksByPlayer || {}),
        [playerKey]: [...rolled],
      },
      fateUsedChecksByPlayer: {
        ...(sessionState.fateUsedChecksByPlayer || {}),
        [playerKey]: [...fate],
      },
      rollResultsByPlayer: {
        ...(sessionState.rollResultsByPlayer || {}),
        [playerKey]: results,
      },
      wpTarget: wp,
      declarationText: declaration,
    };
  }

  async function updateCombatState(updates) {
    if (!activeMission?.scenes?.length) return;
    const index = activeMission.current_scene;
    const current = activeMission.scenes[index]?.combatState || { round: 1, initiatives: [], conditions: {}, fearRating: 0 };
    const next = { ...current, ...updates };
    next.round = Math.max(1, Number(next.round ?? 1) || 1);
    next.fearRating = Math.max(0, Number(next.fearRating ?? 0) || 0);
    next.conditions = (next.conditions && typeof next.conditions === 'object') ? next.conditions : {};
    const nextInitiatives = sortInitiatives(next.initiatives);
    next.initiatives = nextInitiatives;
    if (nextInitiatives.length > 0) {
      const candidate = typeof next.turnKey === 'string' ? next.turnKey : current.turnKey;
      const hasCandidate = candidate && nextInitiatives.some(entry => initiativeKey(entry) === candidate);
      next.turnKey = hasCandidate ? candidate : initiativeKey(nextInitiatives[0]);
    } else {
      next.turnKey = '';
    }
    next.turnUpdatedAt = next.turnKey ? (next.turnUpdatedAt || current.turnUpdatedAt || new Date().toISOString()) : '';
    await patchScene({ combatState: next });
  }

  async function updateSceneCombatState(sceneIndex, updates) {
    if (!activeMission?.scenes?.length) return;
    const current = activeMission.scenes[sceneIndex]?.combatState || { round: 1, initiatives: [], conditions: {}, fearRating: 0 };
    const next = { ...current, ...updates };
    next.round = Math.max(1, Number(next.round ?? 1) || 1);
    next.fearRating = Math.max(0, Number(next.fearRating ?? 0) || 0);
    next.conditions = (next.conditions && typeof next.conditions === 'object') ? next.conditions : {};
    const nextInitiatives = sortInitiatives(next.initiatives);
    next.initiatives = nextInitiatives;
    if (nextInitiatives.length > 0) {
      const candidate = typeof next.turnKey === 'string' ? next.turnKey : current.turnKey;
      const hasCandidate = candidate && nextInitiatives.some(entry => initiativeKey(entry) === candidate);
      next.turnKey = hasCandidate ? candidate : initiativeKey(nextInitiatives[0]);
    } else {
      next.turnKey = '';
    }
    next.turnUpdatedAt = next.turnKey ? (next.turnUpdatedAt || current.turnUpdatedAt || new Date().toISOString()) : '';
    await patchSceneAt(sceneIndex, { combatState: next });
  }

  // ─── Scene control ───────────────────────────────────────────────

  async function setPlayPrompt(prompt) {
    if (!isGM || !activeMission?.scenes?.length) return;
    await patchScene({ playPrompt: prompt });
  }

  function clearPlayPrompt() { setPlayPrompt(null); }

  // #6 — check opener with assignment + decision options
  function openCheckForPlayers(check, opts = {}) {
    const target = clampTarget((check.target || 45) + (check.modifier || 0));
    setPlayPrompt({
      type: 'check',
      title: check.name,
      check: { ...check, target },
      assignedTo: opts.assignedTo || null,
      isDecision: opts.isDecision || false,
      openedAt: new Date().toISOString(),
    });
    setCheckOpenOptions(null);
  }

  function openCombatForPlayers() {
    setPlayPrompt({
      type: 'combat',
      title: `Combat: ${sceneCombat.enemy || 'Current threat'}`,
      combat: sceneCombat,
      openedAt: new Date().toISOString(),
    });
  }

  // #2 — toggle individual reveal element
  async function toggleReveal(key) {
    const current = currentScene?.reveal || {};
    await patchScene({ reveal: { ...current, [key]: !sceneReveal[key] } });
  }

  async function revealAll() {
    await patchScene({ reveal: { title: true, mood: true, description: true, objectives: true, complications: true } });
  }

  async function lockAll() {
    const currentChecks = currentScene?.reveal?.checks || {};
    const lockedChecks = Object.fromEntries(Object.keys(currentChecks).map(k => [k, false]));
    await patchScene({ reveal: { title: false, mood: false, description: false, objectives: false, complications: false, checks: lockedChecks } });
  }

  async function toggleRevealCheck(checkName) {
    const current = currentScene?.reveal || {};
    const currentChecks = current.checks || {};
    await patchScene({ reveal: { ...current, checks: { ...currentChecks, [checkName]: !currentChecks[checkName] } } });
  }

  async function submitGmManualRoll() {
    if (!gmManualRoll) return;
    const { checkName, playerName, value } = gmManualRoll;
    const check = sceneChecks.find(c => c.name === checkName);
    if (!check || !playerName || !value) return;
    const target = clampTarget((check.target || 45) + (check.modifier || 0));
    const roll = parseInt(value);
    if (isNaN(roll) || roll < 1 || roll > 100) return;
    const result = degrees(target, roll);
    try {
      await axios.post('/api/missions/rolls', {
        missionId: activeMission?.id,
        playerName,
        rollType: 'check',
        sceneIndex: activeSceneIndex,
        label: checkName,
        payload: { name: checkName, skill: check.skill, roll, target, ...result },
      });
      await loadRollFeed();
      setGmManualRoll(null);
    } catch (e) { /* non-blocking */ }
  }

  async function toggleSceneRollsUnlocked() {
    await patchScene({ rollsUnlocked: !currentScene?.rollsUnlocked });
  }

  // ─── Combat state helpers ────────────────────────────────────────

  async function rollInitiative() {
    const ab = parseInt(agBonus) || 0;
    const dieRoll = Math.floor(Math.random() * 10) + 1;
    const total = dieRoll + ab;
    const entry = { player: authedPlayer, agBonus: ab, dieRoll, total, type: 'player', ts: Date.now() };
    const existing = Array.isArray(activeMission.scenes[activeMission.current_scene]?.combatState?.initiatives)
      ? activeMission.scenes[activeMission.current_scene].combatState.initiatives : [];
    const idx = existing.findIndex(e => e.player === authedPlayer);
    const next = idx >= 0 ? existing.map((e, i) => (i === idx ? entry : e)) : [...existing, entry];
    await updateCombatState({ initiatives: next });
    await recordMissionRoll('initiative', 'Initiative', { agBonus: ab, dieRoll, total });
  }

  // #3 — add NPC to initiative
  async function addNpcInitiative() {
    const name = npcName.trim();
    if (!name) return;
    const roll = parseInt(npcRoll) || (Math.floor(Math.random() * 10) + 1);
    const entry = { player: name, agBonus: 0, dieRoll: roll, total: roll, type: 'npc', ts: Date.now() };
    const existing = Array.isArray(activeMission.scenes[activeMission.current_scene]?.combatState?.initiatives)
      ? activeMission.scenes[activeMission.current_scene].combatState.initiatives : [];
    await updateCombatState({ initiatives: [...existing, entry] });
    setNpcName('');
    setNpcRoll('');
  }

  async function advanceCombatTurn(delta = 1) {
    const order = sortedCombatInitiatives;
    if (!order.length) return;
    const currentKey = combatState.turnKey && order.some(entry => initiativeKey(entry) === combatState.turnKey)
      ? combatState.turnKey
      : initiativeKey(order[0]);
    let index = order.findIndex(entry => initiativeKey(entry) === currentKey);
    if (index < 0) index = 0;
    let nextIndex = index + delta;
    let nextRound = Math.max(1, Number(combatState.round || 1) || 1);
    if (nextIndex >= order.length) {
      nextIndex = 0;
      nextRound += 1;
    } else if (nextIndex < 0) {
      nextIndex = order.length - 1;
      nextRound = Math.max(1, nextRound - 1);
    }
    await updateCombatState({
      round: nextRound,
      turnKey: initiativeKey(order[nextIndex]),
      turnUpdatedAt: new Date().toISOString(),
    });
  }

  async function toggleCondition(name, condition) {
    const current = activeMission.scenes[activeMission.current_scene]?.combatState || {};
    const existing = (current.conditions || {})[name] || [];
    const next = existing.includes(condition)
      ? existing.filter(c => c !== condition)
      : [...existing, condition];
    await updateCombatState({ conditions: { ...(current.conditions || {}), [name]: next } });
  }

  async function shiftRound(delta) {
    const current = activeMission.scenes[activeMission.current_scene]?.combatState || {};
    await updateCombatState({ round: Math.max(1, (current.round ?? 1) + delta) });
  }

  async function setFearRating(rating) {
    await updateCombatState({ fearRating: rating });
  }

  async function clearCombatState() {
    await updateCombatState({ round: 1, initiatives: [], conditions: {}, fearRating: 0 });
  }

  // ─── Roll actions ────────────────────────────────────────────────

  async function rollSceneCheck(check) {
    const target = clampTarget((check.target || 45) + (check.modifier || 0));
    const roll = d100();
    const result = degrees(target, roll);
    const entry = {
      id: `${Date.now()}-${Math.random()}`,
      name: check.name,
      skill: check.skill,
      target,
      roll,
      rolledBy: authedPlayer,
      ...result,
    };
    const nextRolledChecks = new Set([...rolledChecks, check.name]);
    const nextRollResults = [entry, ...rollResults].slice(0, 5);
    setRolledChecks(nextRolledChecks);
    setRollResults(nextRollResults);
    await recordMissionRoll('check', check.name, {
      sceneTitle: titleForScene(currentScene, activeSceneIndex),
      check,
      target,
      roll,
      ...result,
    });
    await patchActiveSceneSessionState(playerSessionStatePatch({
      rolled: nextRolledChecks,
      results: nextRollResults,
    }));
  }

  function miniState(sceneIndex, scene) {
    const checks = listForScene(scene, 'checks').length ? listForScene(scene, 'checks') : defaultChecksForScene(scene);
    const enemies = sceneEnemyObjects(scene);
    const current = miniRollerState[sceneIndex] || scene?.sessionState?.miniRollerState || {};
    const defaultPlayer = isGM ? (playerList[0] || '') : authedPlayer;
    return {
      player: current.player || defaultPlayer,
      checkName: current.checkName || checks[0]?.name || '',
      standardActionName: current.standardActionName || '',
      skillDifficultyModifier: current.skillDifficultyModifier ?? scene?.skillDifficultyModifier ?? 0,
      skillSituationModifiers: Array.isArray(current.skillSituationModifiers)
        ? current.skillSituationModifiers
        : (Array.isArray(scene?.skillSituationModifiers) ? scene.skillSituationModifiers : []),
      skillCustomModifier: current.skillCustomModifier ?? scene?.skillCustomModifier ?? 0,
      enemyName: current.enemyName || enemies[0]?.name || '',
      attackSkill: current.attackSkill || 'BS',
      modifier: current.modifier ?? 0,
      initPlayer: current.initPlayer || defaultPlayer,
      initAgBonus: current.initAgBonus || '',
      enemyInitAgBonus: current.enemyInitAgBonus || '',
    };
  }

  function setMiniState(sceneIndex, updates) {
    setMiniRollerState(prev => ({
      ...prev,
      [sceneIndex]: { ...(prev[sceneIndex] || {}), ...updates },
    }));
  }

  async function setMiniSkillSettings(sceneIndex, updates) {
    setMiniState(sceneIndex, updates);
    if (!isGM) return;
    await patchSceneAt(sceneIndex, updates);
  }

  function playerCanSeeSkillConditions(scene) {
    return isGM || Boolean(scene?.skillConditionsUnlocked);
  }

  function skillModifierPayloadForScene(scene, state) {
    if (playerCanSeeSkillConditions(scene)) {
      return {
        difficulty: state.skillDifficultyModifier,
        situations: state.skillSituationModifiers,
        custom: state.skillCustomModifier,
      };
    }
    return { difficulty: 0, situations: [], custom: 0 };
  }

  function playerSheet(name) {
    return playerSheets[name]?.tabInfo || {};
  }

  function selectedSkillSituationModifiers(ids = []) {
    const selected = new Set(ids || []);
    return SKILL_SITUATION_MODIFIERS.filter(mod => selected.has(mod.id));
  }

  function targetBreakdownForPlayerCheck(playerName, check, skillModifiers = {}) {
    const tab = playerSheet(playerName);
    const chars = tab.characteristics || {};
    const skill = matchingSheetSkill(tab.skills || {}, check);
    const characteristic = check.characteristic || 'Int';
    const checkModifier = Number(check.modifier || 0);
    const difficultyModifier = Number(skillModifiers.difficulty || 0);
    const situationMods = selectedSkillSituationModifiers(skillModifiers.situations || []);
    const situationTotal = situationMods.reduce((sum, item) => sum + Number(item.value || 0), 0);
    const customModifier = Number(skillModifiers.custom || 0);
    const training = Object.keys(chars).length ? skillTrainingBonus(skill) : 0;
    const base = Object.keys(chars).length
      ? characteristicValue(chars, characteristic, check.target || 45)
      : (check.target || 45);
    const target = clampTarget(base + training + checkModifier + difficultyModifier + situationTotal + customModifier);
    return {
      target,
      base,
      characteristic,
      training,
      trainingLabel: Object.keys(chars).length ? skillTrainingLabel(skill) : 'sheet missing',
      checkModifier,
      difficultyModifier,
      situationMods,
      situationTotal,
      customModifier,
    };
  }

  function hasMiniSkillRolled(sceneIndex, scene, playerName, check) {
    if (!playerName || !check?.name) return false;
    const feedSceneIndex = isGM ? sceneIndex : activeSceneIndex;
    const locked = Boolean(scene?.rollLocks?.skills?.[check.name]?.[playerName]);
    const recorded = rollFeed.some(roll =>
      roll.scene_index === feedSceneIndex &&
      roll.roll_type === 'check' &&
      roll.label === check.name &&
      roll.player_name === playerName &&
      roll.payload?.mini
    );
    return locked || recorded;
  }

  async function rollMiniSceneCheck(sceneIndex, scene) {
    const state = miniState(sceneIndex, scene);
    const checks = listForScene(scene, 'checks').length ? listForScene(scene, 'checks') : defaultChecksForScene(scene);
    const skillChoices = [...checks, ...STANDARD_ACTIONS];
    const check = skillChoices.find(c => c.name === state.checkName) || checks[0] || STANDARD_ACTIONS[0];
    if (!check || !state.player) return;
    if (!scene?.rollsUnlocked && hasMiniSkillRolled(sceneIndex, scene, state.player, check)) return;
    const feedSceneIndex = isGM ? sceneIndex : activeSceneIndex;
    const targetBreakdown = targetBreakdownForPlayerCheck(state.player, check, skillModifierPayloadForScene(scene, state));
    const target = targetBreakdown.target;
    const roll = d100();
    const result = degrees(target, roll);
    const rollLocks = {
      ...(scene.rollLocks || {}),
      skills: {
        ...(scene.rollLocks?.skills || {}),
        [check.name]: {
          ...(scene.rollLocks?.skills?.[check.name] || {}),
          [state.player]: true,
        },
      },
    };
    await patchSceneAt(sceneIndex, {
      rollLocks,
      sessionState: {
        ...(scene.sessionState || {}),
        miniRollerState: state,
        updatedAt: new Date().toISOString(),
      },
    });
    await recordMissionRollForScene(feedSceneIndex, state.player, 'check', check.name, {
      sceneTitle: titleForScene(scene, sceneIndex),
      mini: true,
      check,
      targetBreakdown,
      target,
      roll,
      ...result,
    });
  }

  async function rollMiniPlayerAttack(sceneIndex, scene) {
    const state = miniState(sceneIndex, scene);
    const tab = playerSheet(state.player);
    const chars = tab.characteristics || {};
    const enemies = sceneEnemyObjects(scene);
    const enemy = enemies.find(e => e.name === state.enemyName) || enemies[0] || {};
    if (!state.player) return;
    const currentWounds = Math.max(0, Number(enemy.wounds ?? enemy.woundsTotal ?? enemy.w ?? 0) || 0);
    if (enemy.dead || enemy.defeated || currentWounds <= 0) return;
    const feedSceneIndex = isGM ? sceneIndex : activeSceneIndex;
    const attackSkill = state.attackSkill === 'WS' ? 'WS' : 'BS';
    const target = clampTarget(characteristicValue(chars, attackSkill, 45) + (Number(state.modifier) || 0));
    const roll = d100();
    const result = degrees(target, roll);
    const weapon = firstWeaponDamage(tab, attackSkill);
    const damageRoll = result.success ? rollDamageExpression(weapon.damage) : null;
    const targetTB = Number(enemy.tb ?? combatForScene(scene).targetTB ?? 4) || 0;
    const targetArmour = Number(enemy.armour ?? enemy.ap ?? combatForScene(scene).targetArmour ?? 0) || 0;
    const effectiveArmour = Math.max(0, targetArmour - weapon.pen);
    const appliedDamage = damageRoll ? Math.max(0, damageRoll.total - targetTB - effectiveArmour) : 0;
    const remainingWounds = Math.max(0, currentWounds - appliedDamage);
    const defeated = result.success && remainingWounds <= 0;

    if (result.success) {
      const originalEnemies = Array.isArray(scene.enemies) ? scene.enemies : [];
      const nextEnemies = originalEnemies.map(original => {
        const originalName = typeof original === 'string' ? original : (original.name || original.type);
        if (originalName !== enemy.name) return original;
        const nextEnemy = typeof original === 'string' ? { name: original } : { ...original };
        nextEnemy.wounds = remainingWounds;
        nextEnemy.currentWounds = remainingWounds;
        if (defeated) {
          nextEnemy.dead = true;
          nextEnemy.defeated = true;
          nextEnemy.status = 'dead';
        }
        return nextEnemy;
      });
      await patchSceneAt(sceneIndex, { enemies: nextEnemies });
    }

    await recordMissionRollForScene(feedSceneIndex, state.player, 'attack', `${state.player} vs ${enemy.name || 'target'}`, {
      sceneTitle: titleForScene(scene, sceneIndex),
      mini: true,
      attacker: state.player,
      enemy: enemy.name || 'target',
      weapon: weapon.name,
      using: attackSkill,
      attackRoll: roll,
      target,
      success: result.success,
      dos: result.dos,
      dof: result.dof,
      hits: result.success ? 1 : 0,
      damage: weapon.damage,
      damageRoll: damageRoll ? damageRoll.rolls : [],
      rawDamage: damageRoll ? damageRoll.total : 0,
      appliedDamage,
      targetTB,
      targetArmour,
      effectiveArmour,
      woundsBefore: currentWounds,
      woundsAfter: remainingWounds,
      defeated,
    });
  }

  async function addMiniInitiative(sceneIndex, scene) {
    const state = miniState(sceneIndex, scene);
    const name = state.initPlayer || state.player || authedPlayer;
    if (!name) return;
    const tab = playerSheet(name);
    const ag = characteristicValue(tab.characteristics || {}, 'Ag', 40);
    const ab = state.initAgBonus === '' ? Math.floor(ag / 10) : (parseInt(state.initAgBonus, 10) || 0);
    const dieRoll = Math.floor(Math.random() * 10) + 1;
    const total = dieRoll + ab;
    const entry = { player: name, agBonus: ab, dieRoll, total, type: playerSheets[name] ? 'player' : 'npc', ts: Date.now() };
    const feedSceneIndex = isGM ? sceneIndex : activeSceneIndex;
    const current = scene?.combatState || {};
    const existing = Array.isArray(current.initiatives) ? current.initiatives : [];
    const next = existing.filter(e => e.player !== name).concat(entry);
    await updateSceneCombatState(sceneIndex, { initiatives: next });
    await recordMissionRollForScene(feedSceneIndex, name, 'initiative', 'Initiative', { mini: true, agBonus: ab, dieRoll, total });
  }

  async function addMiniEnemyInitiative(sceneIndex, scene) {
    const state = miniState(sceneIndex, scene);
    const enemies = sceneEnemyObjects(scene);
    const enemy = enemies.find(e => e.name === state.enemyName) || enemies[0];
    if (!enemy) return;
    const ab = state.enemyInitAgBonus === '' ? Math.floor((Number(enemy.ag) || 30) / 10) : (parseInt(state.enemyInitAgBonus, 10) || 0);
    const dieRoll = Math.floor(Math.random() * 10) + 1;
    const total = dieRoll + ab;
    const entry = { player: enemy.name, agBonus: ab, dieRoll, total, type: 'npc', ts: Date.now() };
    const feedSceneIndex = isGM ? sceneIndex : activeSceneIndex;
    const current = scene?.combatState || {};
    const existing = Array.isArray(current.initiatives) ? current.initiatives : [];
    const next = [...existing, entry];
    await updateSceneCombatState(sceneIndex, { initiatives: next });
    await recordMissionRollForScene(feedSceneIndex, enemy.name, 'initiative', 'Initiative', { mini: true, agBonus: ab, dieRoll, total, enemy: true });
  }

  async function advanceMiniCombatTurn(sceneIndex, scene, delta = 1) {
    if (!scene?.combatState) return;
    const order = sortInitiatives(scene.combatState.initiatives);
    if (!order.length) return;
    const currentKey = scene.combatState.turnKey && order.some(entry => initiativeKey(entry) === scene.combatState.turnKey)
      ? scene.combatState.turnKey
      : initiativeKey(order[0]);
    let index = order.findIndex(entry => initiativeKey(entry) === currentKey);
    if (index < 0) index = 0;
    let nextIndex = index + delta;
    let nextRound = Math.max(1, Number(scene.combatState.round || 1) || 1);
    if (nextIndex >= order.length) {
      nextIndex = 0;
      nextRound += 1;
    } else if (nextIndex < 0) {
      nextIndex = order.length - 1;
      nextRound = Math.max(1, nextRound - 1);
    }
    await updateSceneCombatState(sceneIndex, {
      round: nextRound,
      turnKey: initiativeKey(order[nextIndex]),
      turnUpdatedAt: new Date().toISOString(),
    });
  }

  // #7 — fear test (Finding #2: penalty auto-applied)
  const fearPenaltyValue = FEAR_RATINGS[combatState.fearRating]?.penaltyValue ?? 0;
  const effectiveWP = Math.max(1, (parseInt(wpTarget) || 60) + fearPenaltyValue);

  async function rollFearTest() {
    const target = clampTarget(effectiveWP);
    const roll = d100();
    const result = degrees(target, roll);
    const entry = {
      id: `${Date.now()}-${Math.random()}`,
      name: 'Fear Test',
      skill: 'Willpower',
      target,
      roll,
      rolledBy: authedPlayer,
      ...result,
    };
    const nextRolledChecks = new Set([...rolledChecks, '__fear__']);
    const nextRollResults = [entry, ...rollResults].slice(0, 5);
    setRolledChecks(nextRolledChecks);
    setRollResults(nextRollResults);
    await recordMissionRoll('fear', 'Fear Test', { target, roll, ...result });
    await patchActiveSceneSessionState(playerSessionStatePatch({
      rolled: nextRolledChecks,
      results: nextRollResults,
    }));
  }

  // #8 — decision declare
  async function rollDecision() {
    const text = declarationText.trim();
    if (!text) return;
    const nextRolledChecks = new Set([...rolledChecks, playPrompt?.check?.name || '__decision__']);
    setRolledChecks(nextRolledChecks);
    await recordMissionRoll('decision', playPrompt?.title || 'Decision', { declaration: text });
    setDeclarationText('');
    await patchActiveSceneSessionState(playerSessionStatePatch({
      rolled: nextRolledChecks,
      declaration: '',
    }));
  }

  // #9 — fate point re-roll
  async function spendFate(checkName) {
    const nextFateUsedChecks = new Set([...fateUsedChecks, checkName]);
    const nextRolledChecks = new Set(rolledChecks);
    nextRolledChecks.delete(checkName);
    setFateUsedChecks(nextFateUsedChecks);
    setRolledChecks(nextRolledChecks);
    await patchActiveSceneSessionState(playerSessionStatePatch({
      rolled: nextRolledChecks,
      fate: nextFateUsedChecks,
    }));
  }

  function openSceneInRoller() {
    const context = {
      sceneTitle: titleForScene(currentScene, activeSceneIndex),
      enemy: sceneCombat.enemy || 'Custom/None',
      weapon: sceneCombat.weapon || '',
      bs: Number(sceneCombat.bs || 45),
      ws: Number(sceneCombat.ws || 45),
      modifier: Number(sceneCombat.modifier || 0),
      damage: sceneCombat.damage || '1d10+5',
      targetTB: Number(sceneCombat.targetTB || 4),
      targetArmour: Number(sceneCombat.targetArmour || 5),
      mode: sceneCombat.mode || 'single',
      rof: Number(sceneCombat.rof || 1),
      tearing: Boolean(sceneCombat.tearing),
      pen: Number(sceneCombat.pen || 0),
      reliable: Boolean(sceneCombat.reliable),
    };
    window.localStorage.setItem(MISSION_ROLL_CONTEXT_KEY, JSON.stringify(context));
    window.dispatchEvent(new CustomEvent('dw:mission-roll-context'));
    window.dispatchEvent(new CustomEvent('dw:open-roller'));
  }

  function renderMiniSceneRoller(scene, sceneIndex, opts = {}) {
    if (!scene) return null;
    const checks = listForScene(scene, 'checks').length ? listForScene(scene, 'checks') : defaultChecksForScene(scene);
    const skillChoices = [...checks, ...STANDARD_ACTIONS];
    const enemies = sceneEnemyObjects(scene);
    const state = miniState(sceneIndex, scene);
    const combatStateForScene = scene.combatState || {};
    const initiatives = Array.isArray(combatStateForScene.initiatives) ? combatStateForScene.initiatives : [];
    const sortedInitiatives = sortInitiatives(initiatives);
    const miniTurnIndex = sortedInitiatives.length > 0
      ? Math.max(0, sortedInitiatives.findIndex(entry => initiativeKey(entry) === combatStateForScene.turnKey))
      : -1;
    const miniCurrentTurn = miniTurnIndex >= 0 ? sortedInitiatives[miniTurnIndex] || null : null;
    const miniNextTurn = miniTurnIndex >= 0 && sortedInitiatives.length > 1
      ? sortedInitiatives[(miniTurnIndex + 1) % sortedInitiatives.length]
      : null;
    const selectedCheck = skillChoices.find(check => check.name === state.checkName) || checks[0] || STANDARD_ACTIONS[0];
    const selectedPlayer = state.player || (isGM ? playerList[0] : authedPlayer);
    const skillModifiersVisible = playerCanSeeSkillConditions(scene);
    const skillBreakdown = selectedCheck && selectedPlayer
      ? targetBreakdownForPlayerCheck(selectedPlayer, selectedCheck, skillModifierPayloadForScene(scene, state))
      : null;
    const skillTarget = skillBreakdown?.target ?? null;
    const skillAlreadyRolled = Boolean(selectedCheck && selectedPlayer && hasMiniSkillRolled(sceneIndex, scene, selectedPlayer, selectedCheck));
    const skillLocked = skillAlreadyRolled && !scene?.rollsUnlocked;
    const feedSceneIndex = isGM ? sceneIndex : activeSceneIndex;
    const selectedEnemy = enemies.find(enemy => enemy.name === state.enemyName) || enemies[0] || null;
    const targetDead = Boolean(selectedEnemy?.dead || selectedEnemy?.defeated || Number(selectedEnemy?.wounds ?? selectedEnemy?.woundsTotal ?? selectedEnemy?.w ?? 1) <= 0);
    const loggedInWounds = woundsLabel(playerSheet(authedPlayer).wounds);
    const selectedPlayerWounds = woundsLabel(playerSheet(selectedPlayer).wounds);
    const targetWounds = selectedEnemy ? woundsLabel(selectedEnemy.wounds ?? selectedEnemy.woundsTotal ?? selectedEnemy.w) : 'W ?';
    const latestSkillRoll = rollFeed.find(roll =>
      roll.scene_index === feedSceneIndex &&
      roll.roll_type === 'check' &&
      roll.payload?.mini &&
      (!selectedCheck || roll.label === selectedCheck.name)
    );
    const latestCombatRoll = rollFeed.find(roll =>
      roll.scene_index === feedSceneIndex &&
      roll.roll_type === 'attack' &&
      roll.payload?.mini &&
      (!selectedEnemy || roll.payload?.enemy === selectedEnemy.name)
    );

	    return (
		      <div className={`min-w-0 overflow-hidden rounded-lg border border-cyan-700/40 bg-cyan-950/10 p-3 sm:p-4 ${opts.compact ? 'mt-3' : ''}`}>
		        <div className="mb-3 flex min-w-0 flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
		          <div className="min-w-0">
		            <h4 className="text-sm font-semibold uppercase tracking-wide text-cyan-200">Mini Roller</h4>
		            <p className="break-words text-xs text-slate-400">Scene skill, combat and initiative. You: {authedPlayer} · {loggedInWounds}</p>
		          </div>
	          <div className="flex shrink-0 flex-wrap items-center gap-2">
	            <span className="rounded bg-slate-950 px-2 py-1 text-xs text-slate-400">
	              {initiatives.length} initiative{initiatives.length === 1 ? '' : 's'}
	            </span>
	            {sortedInitiatives.length > 0 && (
	              <>
	                <span className="rounded bg-slate-900/70 px-2 py-1 text-xs text-slate-300">
	                  Now: <span className="font-semibold text-slate-100">{miniCurrentTurn ? turnLabel(miniCurrentTurn) : 'None'}</span>
	                </span>
	                <span className="rounded bg-slate-900/70 px-2 py-1 text-xs text-slate-300">
	                  Next: <span className="font-semibold text-slate-100">{miniNextTurn ? turnLabel(miniNextTurn) : 'None'}</span>
	                </span>
	              </>
	            )}
	            {isGM && (
	              <button
	                onClick={() => patchSceneAt(sceneIndex, { rollsUnlocked: !scene?.rollsUnlocked })}
	                className={`rounded px-2 py-1 text-xs ${scene?.rollsUnlocked ? 'bg-amber-700 text-white hover:bg-amber-600' : 'bg-slate-800 text-slate-300 hover:bg-slate-700'}`}
	              >
	                {scene?.rollsUnlocked ? 'Extra Rolls On' : 'Unlock Rolls'}
	              </button>
	            )}
	            {isGM && sortedInitiatives.length > 0 && (
	              <>
	                <button
	                  onClick={() => advanceMiniCombatTurn(sceneIndex, scene, -1)}
	                  className="rounded border border-slate-600 bg-slate-800 px-2 py-1 text-xs text-slate-200 hover:bg-slate-700"
	                >
	                  Prev turn
	                </button>
	                <button
	                  onClick={() => advanceMiniCombatTurn(sceneIndex, scene, 1)}
	                  className="rounded bg-cyan-700 px-2 py-1 text-xs text-white hover:bg-cyan-600"
	                >
	                  Next turn
	                </button>
	              </>
	            )}
	          </div>
	        </div>

        <div className="grid min-w-0 gap-2">
          <div className="min-w-0 rounded border border-slate-700 bg-slate-950/40 p-3">
            <div className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-300">Skill</div>
            <div className="grid min-w-0 gap-2 md:grid-cols-2 xl:grid-cols-4">
              {isGM ? (
                <select
                  value={state.player}
                  onChange={e => setMiniState(sceneIndex, { player: e.target.value, initPlayer: e.target.value })}
                  className="min-w-0 w-full rounded border border-slate-600 bg-slate-900 px-2 py-2 text-sm"
                >
                  {playerList.map(name => <option key={name} value={name}>{name}</option>)}
                </select>
              ) : (
                <div className="min-w-0 break-words rounded border border-slate-700 bg-slate-900 px-2 py-2 text-sm capitalize text-slate-200">
                  {authedPlayer}
                </div>
              )}
              <select
                value={state.checkName}
                onChange={e => setMiniState(sceneIndex, { checkName: e.target.value })}
                className="min-w-0 w-full rounded border border-slate-600 bg-slate-900 px-2 py-2 text-sm"
              >
                {checks.map(check => <option key={check.name} value={check.name}>{check.name}</option>)}
              </select>
              <label className="grid gap-1 text-xs text-slate-400">
                Standard actions
                <select
                  value={STANDARD_ACTIONS.some(action => action.name === state.checkName) ? state.checkName : ''}
                  onChange={e => setMiniState(sceneIndex, { checkName: e.target.value || checks[0]?.name || '' })}
                  className="min-w-0 w-full rounded border border-slate-600 bg-slate-900 px-2 py-2 text-sm text-slate-100"
                >
                  <option value="">Use scene skill</option>
                  {STANDARD_ACTIONS.map(action => (
                    <option key={action.name} value={action.name}>{action.name} · {action.skill}</option>
                  ))}
                </select>
              </label>
              {isGM && (
	                <div className="grid min-w-0 gap-2 rounded border border-slate-800 bg-slate-900/50 p-2 md:col-span-2 xl:col-span-4">
	                  <div className="flex min-w-0 flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
	                    <label className="text-xs font-semibold uppercase tracking-wide text-slate-400">Skill difficulty</label>
	                    <button
	                      type="button"
	                      onClick={() => patchSceneAt(sceneIndex, { skillConditionsUnlocked: !scene?.skillConditionsUnlocked })}
	                      className={`rounded px-2 py-1 text-[11px] sm:shrink-0 ${scene?.skillConditionsUnlocked ? 'bg-emerald-700 text-white hover:bg-emerald-600' : 'bg-slate-800 text-slate-300 hover:bg-slate-700'}`}
                    >
                      {scene?.skillConditionsUnlocked ? 'Conditions Visible' : 'Show Conditions'}
                    </button>
                  </div>
                  <select
	                    aria-label="Skill difficulty"
	                    value={state.skillDifficultyModifier}
	                    onChange={e => setMiniSkillSettings(sceneIndex, { skillDifficultyModifier: Number(e.target.value) })}
	                    className="min-w-0 w-full rounded border border-slate-600 bg-slate-900 px-2 py-2 text-sm"
                  >
                    {SKILL_DIFFICULTIES.map(diff => (
                      <option key={diff.label} value={diff.value}>
                        {diff.label} ({diff.value >= 0 ? '+' : ''}{diff.value})
                      </option>
                    ))}
                  </select>
                  <div className="flex flex-wrap gap-1">
                    {SKILL_SITUATION_MODIFIERS.map(mod => {
                      const active = state.skillSituationModifiers.includes(mod.id);
                      const nextSituations = active
                        ? state.skillSituationModifiers.filter(id => id !== mod.id)
                        : [...state.skillSituationModifiers, mod.id];
                      return (
                        <button
                          key={mod.id}
                          type="button"
                          onClick={() => setMiniSkillSettings(sceneIndex, { skillSituationModifiers: nextSituations })}
                          className={`rounded px-2 py-1 text-[11px] ${active ? 'bg-cyan-700 text-white' : 'bg-slate-800 text-slate-300 hover:bg-slate-700'}`}
                        >
                          {mod.label} {mod.value >= 0 ? '+' : ''}{mod.value}
                        </button>
                      );
                    })}
                  </div>
                  <label className="grid gap-1 text-xs text-slate-400">
                    Custom modifier
                    <input
                      aria-label="Skill custom modifier"
                      type="number"
                      value={state.skillCustomModifier}
                      onChange={e => setMiniSkillSettings(sceneIndex, { skillCustomModifier: Number(e.target.value || 0) })}
                      className="rounded border border-slate-600 bg-slate-900 px-2 py-1.5 text-sm text-slate-100"
                    />
                  </label>
                </div>
              )}
	              {selectedCheck && (
		                <div className="min-w-0 rounded border border-slate-800 bg-slate-900/70 px-2 py-2 text-xs text-slate-300 md:col-span-2">
		                  <div className="flex min-w-0 items-center justify-between gap-2">
		                    <span className="min-w-0 truncate font-semibold text-cyan-200">{selectedCheck.skill || 'Skill'} ({selectedCheck.characteristic || 'Characteristic'})</span>
	                    {skillTarget && <span data-testid="skill-target-number" className="shrink-0 rounded bg-cyan-950 px-2 py-0.5 font-bold tabular-nums text-cyan-100">TN {skillTarget}</span>}
	                  </div>
	                  {selectedCheck.modifier ? (
	                    <div className="mt-1 text-slate-500">Scene modifier {selectedCheck.modifier > 0 ? '+' : ''}{selectedCheck.modifier}</div>
	                  ) : null}
                </div>
              )}
		              <button
		                onClick={() => rollMiniSceneCheck(sceneIndex, scene)}
		                disabled={!selectedCheck || !state.player || skillLocked}
		                title={skillLocked ? 'This player already rolled this skill check. GM can unlock more rolls.' : undefined}
			                className="h-full w-full rounded bg-emerald-700 px-3 py-2 text-sm hover:bg-emerald-600 disabled:opacity-40"
		              >
		                {skillLocked ? 'Already Rolled' : `Roll Skill ${skillTarget ? `(target ${skillTarget})` : ''}`}
		              </button>
		              {skillAlreadyRolled && (
			                <div className={`break-words rounded px-2 py-1 text-xs md:col-span-2 ${scene?.rollsUnlocked ? 'bg-amber-950/40 text-amber-200' : 'bg-slate-900/60 text-slate-400'}`}>
		                  {scene?.rollsUnlocked ? 'GM unlocked extra rolls for this scene.' : 'Locked: 1 skill roll per player for this check.'}
		                </div>
		              )}
	              {latestSkillRoll ? (
		                <div className={`break-words rounded px-2 py-1 text-xs md:col-span-2 ${latestSkillRoll.payload?.success ? 'bg-emerald-950/40 text-emerald-200' : 'bg-rose-950/40 text-rose-200'}`}>
	                  {rollSummary(latestSkillRoll)}
	                </div>
	              ) : (
	                <div className="rounded bg-slate-900/40 px-2 py-1 text-xs text-slate-500 md:col-span-2">No skill roll yet.</div>
	              )}
	            </div>
	          </div>

	          <div className="min-w-0 rounded border border-slate-700 bg-slate-950/40 p-3">
	            <div className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-300">Combat</div>
	            <div className="grid min-w-0 gap-2 md:grid-cols-2 xl:grid-cols-[minmax(190px,1fr)_minmax(220px,1fr)_minmax(190px,1fr)_140px_minmax(220px,1.2fr)]">
	              <select
	                value={state.enemyName}
	                onChange={e => setMiniState(sceneIndex, { enemyName: e.target.value })}
	                className="min-w-0 w-full rounded border border-slate-600 bg-slate-900 px-2 py-2 text-sm"
              >
	                {enemies.length === 0 && <option value="">No scene enemies</option>}
	                {enemies.map(enemy => {
	                  const dead = enemy.dead || enemy.defeated || Number(enemy.wounds ?? enemy.woundsTotal ?? enemy.w ?? 1) <= 0;
	                  return <option key={enemy.name} value={enemy.name}>{enemy.name} ({woundsLabel(enemy.wounds ?? enemy.woundsTotal ?? enemy.w)}){dead ? ' DEAD' : ''}</option>;
	                })}
	              </select>
		              <div className="grid min-w-0 gap-2 text-xs sm:grid-cols-2">
		                <div className="min-w-0 break-words rounded border border-slate-800 bg-slate-900/60 px-2 py-1 text-slate-400">
		                  Player Wounds: <span className="text-slate-200">{selectedPlayerWounds}</span>
		                </div>
		                <div className="min-w-0 break-words rounded border border-slate-800 bg-slate-900/60 px-2 py-1 text-slate-400">
		                  Target Wounds: <span className="text-slate-200">{targetWounds}</span>
		                </div>
		              </div>
		              <div className="grid min-w-0 grid-cols-[minmax(0,1fr)_80px] gap-2 sm:grid-cols-[minmax(0,1fr)_90px]">
	                <select
                  value={state.attackSkill}
                  onChange={e => setMiniState(sceneIndex, { attackSkill: e.target.value })}
	                  className="min-w-0 w-full rounded border border-slate-600 bg-slate-900 px-2 py-2 text-sm"
                >
                  <option value="BS">Ranged BS</option>
                  <option value="WS">Melee WS</option>
                </select>
                <input
                  type="number"
                  value={state.modifier}
                  onChange={e => setMiniState(sceneIndex, { modifier: parseInt(e.target.value || '0', 10) })}
	                  className="min-w-0 w-full rounded border border-slate-600 bg-slate-900 px-2 py-2 text-sm"
                  title="Attack modifier"
                />
              </div>
	              <button
	                onClick={() => rollMiniPlayerAttack(sceneIndex, scene)}
	                disabled={!state.player || enemies.length === 0 || targetDead}
		                className="h-full w-full rounded bg-red-800 px-3 py-2 text-sm hover:bg-red-700 disabled:opacity-40"
	              >
	                {targetDead ? 'Target Dead' : 'Roll Attack'}
	              </button>
	              {latestCombatRoll ? (
		                <div className={`break-words rounded px-2 py-1 text-xs md:col-span-2 xl:col-span-1 ${latestCombatRoll.payload?.success ? 'bg-emerald-950/40 text-emerald-200' : 'bg-rose-950/40 text-rose-200'}`}>
	                  {rollSummary(latestCombatRoll)}
	                </div>
	              ) : (
	                <div className="rounded bg-slate-900/40 px-2 py-1 text-xs text-slate-500 md:col-span-2 xl:col-span-1">No combat roll yet.</div>
	              )}
	            </div>
	          </div>

	          <div className="min-w-0 rounded border border-slate-700 bg-slate-950/40 p-3">
	            <div className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-300">Initiative</div>
	            <div className="grid min-w-0 gap-2 md:grid-cols-[minmax(180px,1fr)_160px_140px] xl:grid-cols-[minmax(240px,1fr)_180px_160px]">
              <select
                value={state.initPlayer}
                onChange={e => setMiniState(sceneIndex, { initPlayer: e.target.value })}
                disabled={!isGM}
	                className="min-w-0 w-full rounded border border-slate-600 bg-slate-900 px-2 py-2 text-sm"
              >
                {(isGM ? playerList : [authedPlayer]).map(name => <option key={name} value={name}>{name}</option>)}
              </select>
              <input
                type="number"
                placeholder="Ag bonus auto"
                value={state.initAgBonus}
                onChange={e => setMiniState(sceneIndex, { initAgBonus: e.target.value })}
	                className="min-w-0 w-full rounded border border-slate-600 bg-slate-900 px-2 py-2 text-sm"
              />
              <button
                onClick={() => addMiniInitiative(sceneIndex, scene)}
                disabled={!state.initPlayer}
	                className="h-full w-full rounded bg-blue-700 px-3 py-2 text-sm hover:bg-blue-600 disabled:opacity-40"
              >
                Roll Initiative
              </button>
            </div>
            {isGM && (
              <div className="mt-2 grid min-w-0 gap-2 md:grid-cols-[minmax(180px,1fr)_160px_140px] xl:grid-cols-[minmax(240px,1fr)_180px_160px]">
                <div className="min-w-0 truncate rounded border border-slate-700 bg-slate-900 px-2 py-2 text-sm text-slate-300">
                  Enemy: <span className="text-slate-100">{selectedEnemy ? selectedEnemy.name : 'No enemy selected'}</span>
                </div>
                <input
                  type="number"
                  placeholder="Ag bonus auto"
                  value={state.enemyInitAgBonus}
                  onChange={e => setMiniState(sceneIndex, { enemyInitAgBonus: e.target.value })}
                  className="min-w-0 w-full rounded border border-slate-600 bg-slate-900 px-2 py-2 text-sm"
                />
                <button
                  onClick={() => addMiniEnemyInitiative(sceneIndex, scene)}
                  disabled={!selectedEnemy}
                  className="h-full w-full rounded bg-purple-700 px-3 py-2 text-sm hover:bg-purple-600 disabled:opacity-40"
                >
                  Roll Enemy Initiative
                </button>
              </div>
            )}
	          </div>
	        </div>

	        {selectedCheck && (skillBreakdown || selectedCheck.rulesNote || selectedCheck.reward) && (
	          <div className="mt-3 min-w-0 rounded border border-slate-700 bg-slate-950/50 p-3 text-xs text-slate-300">
	            <div className="mb-2 flex min-w-0 flex-wrap items-center justify-between gap-2">
	              <span className="font-semibold uppercase tracking-wide text-cyan-200">Skill Details</span>
	              {skillTarget && <span className="rounded bg-cyan-950 px-2 py-0.5 font-bold tabular-nums text-cyan-100">Target {skillTarget}</span>}
	            </div>
	            {skillBreakdown && (
	              <div className="grid min-w-0 gap-2 sm:grid-cols-2 lg:grid-cols-3">
	                <div className="rounded bg-slate-900/70 px-2 py-1">Base {skillBreakdown.characteristic}: <span className="text-slate-100">{skillBreakdown.base}</span></div>
	                <div className="rounded bg-slate-900/70 px-2 py-1">Training: <span className="text-slate-100">{skillBreakdown.trainingLabel} ({skillBreakdown.training >= 0 ? '+' : ''}{skillBreakdown.training})</span></div>
	                <div className="rounded bg-slate-900/70 px-2 py-1">Scene: <span className="text-slate-100">{skillBreakdown.checkModifier >= 0 ? '+' : ''}{skillBreakdown.checkModifier}</span></div>
	                {skillModifiersVisible && (
	                  <>
	                    <div className="rounded bg-slate-900/70 px-2 py-1">Difficulty: <span className="text-slate-100">{skillBreakdown.difficultyModifier >= 0 ? '+' : ''}{skillBreakdown.difficultyModifier}</span></div>
	                    <div className="rounded bg-slate-900/70 px-2 py-1">Conditions: <span className="text-slate-100">{skillBreakdown.situationTotal >= 0 ? '+' : ''}{skillBreakdown.situationTotal}</span></div>
	                    <div className="rounded bg-slate-900/70 px-2 py-1">Custom: <span className="text-slate-100">{skillBreakdown.customModifier >= 0 ? '+' : ''}{skillBreakdown.customModifier}</span></div>
	                  </>
	                )}
	              </div>
	            )}
	            {skillModifiersVisible && skillBreakdown?.situationMods.length > 0 && (
	              <div className="mt-2 flex flex-wrap gap-1">
	                {skillBreakdown.situationMods.map(mod => (
	                  <span key={`${mod.label}-${mod.value}`} className="rounded bg-slate-800 px-1.5 py-0.5 text-[11px] text-slate-300">
	                    {mod.label} {mod.value ? `${mod.value > 0 ? '+' : ''}${mod.value}` : '+0'}
	                  </span>
	                ))}
	              </div>
	            )}
	            {!skillModifiersVisible && (
	              <div className="mt-2 rounded bg-slate-900/70 px-2 py-1 text-[11px] text-slate-500">Conditions hidden by GM.</div>
	            )}
	            {selectedCheck.rulesNote && (
	              <div className="mt-2 break-words rounded bg-slate-900/70 px-2 py-1 text-slate-400">{selectedCheck.rulesNote}</div>
	            )}
	            {selectedCheck.reward && (
	              <div className="mt-2 break-words rounded bg-amber-950/30 px-2 py-1 text-amber-200">Success: {selectedCheck.reward}</div>
	            )}
	          </div>
	        )}
	
	        {initiatives.length > 0 && (
	          <div className="mt-3 grid min-w-0 gap-1">
            {sortedInitiatives.map((entry, i) => {
              const key = initiativeKey(entry);
              const isCurrentTurn = miniTurnIndex >= 0 ? key === initiativeKey(miniCurrentTurn) : i === 0;
              const isNextTurn = miniNextTurn ? key === initiativeKey(miniNextTurn) : false;
              return (
	              <div key={key || `${entry.player}-${i}`} className={`flex min-w-0 flex-wrap items-center gap-2 rounded px-2 py-1 text-xs ${isCurrentTurn ? 'ring-1 ring-cyan-400/80 bg-cyan-950/40' : 'bg-slate-950/60'}`}>
	                <span className="w-5 text-center text-slate-500">{i + 1}</span>
	                {isCurrentTurn && <span className="rounded bg-cyan-700 px-1.5 py-0.5 text-[11px] font-semibold text-white">Now</span>}
	                {!isCurrentTurn && isNextTurn && <span className="rounded bg-slate-700 px-1.5 py-0.5 text-[11px] font-semibold text-slate-200">Next</span>}
	                <span className="min-w-0 flex-1 break-words capitalize">{entry.player}</span>
                <span className="font-semibold text-cyan-200">{entry.total}</span>
                <span className="text-slate-500">d10:{entry.dieRoll} + Ab:{entry.agBonus}</span>
                {isGM && (
                  <button
                    onClick={() => updateSceneCombatState(sceneIndex, { initiatives: initiatives.filter(e => !(e.player === entry.player && e.ts === entry.ts)) })}
                    className="rounded bg-slate-800 px-1.5 py-0.5 text-slate-400 hover:bg-slate-700"
                  >
                    x
                  </button>
                )}
              </div>
            );
            })}
          </div>
        )}
      </div>
    );
  }

  async function setMissionActive(id = selectedMissionId) {
    if (!id) return;
    setSaving(true);
    setError('');
    try {
      const res = await axios.post(`/api/missions/${id}/active`);
      const mission = normalizeMission(res.data?.mission);
      setActiveMission(mission);
      setSelectedMissionId(String(id));
      setMessage('Mission set for the table.');
      // Finding #1: initialise revealed:false on all scenes that haven't been set yet,
      // so the GM must explicitly reveal each scene before players see it.
      if (mission?.scenes?.some(s => s.revealed === undefined)) {
        const initScenes = mission.scenes.map(s => ({
          ...s,
          revealed: s.revealed !== undefined ? s.revealed : false,
        }));
        await axios.put(`/api/missions/${mission.id}/progress`, {
          currentScene: mission.current_scene,
          scenes: initScenes,
        });
      }
      await loadMissions();
    } catch (e) {
      setError(`Failed to set mission: ${e.response?.data?.error || e.message}`);
    } finally {
      setSaving(false);
    }
  }

  function setCurrentScene(index) { saveProgress(index); }

  function toggleSceneComplete(index) {
    if (!activeMission?.scenes) return;
    const nextScenes = activeMission.scenes.map((scene, i) =>
      i === index ? { ...scene, completed: !scene.completed } : scene
    );
    saveProgress(activeMission.current_scene, nextScenes);
  }

  async function toggleExtraChallenge(challengeIndex) {
    if (!isGM || !activeMission?.scenes?.length || !currentScene) return;
    const nextChallenges = sceneExtraChallenges.map((challenge, index) =>
      index === challengeIndex ? { ...challenge, completed: !challenge.completed } : challenge
    );
    await patchScene({ extraChallenges: nextChallenges });
  }

  // #11 — save outcome notes to last scene
  async function saveOutcomeNotes() {
    if (!isGM || !activeMission?.scenes?.length) return;
    const lastIdx = activeMission.scenes.length - 1;
    const nextScenes = activeMission.scenes.map((scene, i) =>
      i === lastIdx ? { ...scene, outcome: outcomeNotes } : scene
    );
    await saveProgress(activeMission.current_scene, nextScenes);
  }

  // ─── Scene editor helpers ────────────────────────────────────────

  function openSceneEdit() {
    if (!currentScene) return;
    setSceneEdit({
      title:          currentScene.title || currentScene.name || '',
      type:           currentScene.type || 'combat',
      mood:           currentScene.mood || '',
      description:    currentScene.description || currentScene.briefing || currentScene.summary || '',
      endingText:     endingTextForScene(currentScene),
      checks:         (Array.isArray(currentScene.checks) ? currentScene.checks : []).map(c => ({ ...c })),
      enemies:        (Array.isArray(currentScene.enemies) ? currentScene.enemies : []).map(e => ({ ...e })),
      objectives:     (Array.isArray(currentScene.objectives) ? currentScene.objectives : []).join('\n'),
      complications:  (Array.isArray(currentScene.complications) ? currentScene.complications : []).join('\n'),
    });
    setEditingScene(true);
  }

  async function saveSceneEdit() {
    if (!sceneEdit) return;
    const toArr = (str) => str.split('\n').map(s => s.trim()).filter(Boolean);
    await patchScene({
      title:          sceneEdit.title.trim() || undefined,
      name:           sceneEdit.title.trim() || undefined,
      type:           sceneEdit.type,
      mood:           sceneEdit.mood,
      description:    sceneEdit.description,
      briefing:       sceneEdit.description,
      endingText:     sceneEdit.endingText,
      checks:         sceneEdit.checks,
      enemies:        sceneEdit.enemies,
      objectives:     toArr(sceneEdit.objectives),
      complications:  toArr(sceneEdit.complications),
    });
    setEditingScene(false);
    setSceneEdit(null);
  }

  function seField(field, value) {
    setSceneEdit(prev => ({ ...prev, [field]: value }));
  }

  function seCheck(i, field, value) {
    setSceneEdit(prev => {
      const checks = prev.checks.map((c, idx) => idx === i ? { ...c, [field]: value } : c);
      return { ...prev, checks };
    });
  }

  function addCheck() {
    setSceneEdit(prev => ({
      ...prev,
      checks: [...prev.checks, { name: 'New Check', skill: 'Awareness', characteristic: 'Per', target: 45, modifier: 0, reward: '' }],
    }));
  }

  function removeCheck(i) {
    setSceneEdit(prev => ({ ...prev, checks: prev.checks.filter((_, idx) => idx !== i) }));
  }

  function seEnemy(i, field, value) {
    setSceneEdit(prev => {
      const enemies = prev.enemies.map((e, idx) => idx === i ? { ...e, [field]: value } : e);
      return { ...prev, enemies };
    });
  }

  function addEnemy() {
    setSceneEdit(prev => ({
      ...prev,
      enemies: [...prev.enemies, { name: 'Enemy', wounds: 10, bs: 45, ws: 45, damage: '1d10+4', ap: 0 }],
    }));
  }

  function removeEnemy(i) {
    setSceneEdit(prev => ({ ...prev, enemies: prev.enemies.filter((_, idx) => idx !== i) }));
  }

  // ─── Render ──────────────────────────────────────────────────────

  if (!authedPlayer) {
    return (
      <section className="min-h-screen bg-slate-900 text-slate-100 p-4 md:p-8">
        <div className="mx-auto max-w-6xl">
          <h1 className="text-3xl font-bold">Mission</h1>
          <p className="mt-3 text-slate-300">Log in to see the active Deathwatch mission.</p>
        </div>
      </section>
    );
  }

  return (
    <section className="min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-800 text-slate-100 p-4 md:p-8">
      <div className="mx-auto max-w-6xl space-y-5">

        {/* Header */}
        <div className="flex flex-col gap-3 md:flex-row md:items-end md:justify-between">
          <div>
            <p className="text-xs uppercase tracking-wide text-blue-300">Deathwatch Mission Table</p>
            <h1 className="text-3xl font-bold">Active Mission</h1>
          </div>
          <button onClick={loadMissions} className="self-start rounded bg-slate-700 px-3 py-2 text-sm hover:bg-slate-600">
            Refresh
          </button>
        </div>

        {/* Next game date banner */}
        <div className="flex items-center gap-3 rounded-lg border border-amber-500/30 bg-amber-950/20 px-4 py-3">
          <span className="text-lg">⚔️</span>
          <div>
            <span className="text-xs uppercase tracking-wide text-amber-400">Næste spildag</span>
            <p className="font-semibold text-amber-100">Lørdag 28. juni 2026 — Deathwatch session</p>
          </div>
        </div>

        {error && <div className="rounded border border-red-500/40 bg-red-950/40 p-3 text-sm text-red-200">{error}</div>}
        {message && <div className="rounded border border-emerald-500/30 bg-emerald-950/30 p-3 text-sm text-emerald-200">{message}</div>}

        {/* #11 — Mission complete banner */}
        {missionComplete && (
          <div className="rounded-lg border border-emerald-500/40 bg-emerald-950/20 p-4">
            <div className="flex items-center justify-between gap-3">
              <div>
                <h2 className="text-lg font-bold text-emerald-200">Mission Complete</h2>
                <p className="text-sm text-emerald-100/70">{activeMission.name} — all {activeMission.scenes.length} scenes concluded.</p>
              </div>
              <button onClick={() => setShowMissionReport(v => !v)} className="rounded bg-emerald-800 px-3 py-2 text-sm hover:bg-emerald-700">
                {showMissionReport ? 'Hide Report' : 'View Report'}
              </button>
            </div>
            {showMissionReport && (
              <div className="mt-4 space-y-4">
                <div className="grid gap-3 sm:grid-cols-4 text-sm">
                  <div className="rounded border border-slate-700 bg-slate-900/60 p-3 text-center">
                    <div className="text-2xl font-bold text-emerald-300">{activeMission.scenes.length}</div>
                    <div className="text-xs uppercase tracking-wide text-slate-400 mt-1">Scenes</div>
                  </div>
                  <div className="rounded border border-slate-700 bg-slate-900/60 p-3 text-center">
                    <div className="text-2xl font-bold text-blue-300">{rollStats.total}</div>
                    <div className="text-xs uppercase tracking-wide text-slate-400 mt-1">Checks Rolled</div>
                  </div>
                  <div className="rounded border border-slate-700 bg-slate-900/60 p-3 text-center">
                    <div className="text-2xl font-bold text-amber-300">
                      {rollStats.total > 0 ? Math.round((rollStats.successes / rollStats.total) * 100) : 0}%
                    </div>
                    <div className="text-xs uppercase tracking-wide text-slate-400 mt-1">Success Rate</div>
                  </div>
                  <div className="rounded border border-slate-700 bg-slate-900/60 p-3 text-center">
                    <div className="text-2xl font-bold text-cyan-300">{missionXpEstimate.total}</div>
                    <div className="text-xs uppercase tracking-wide text-slate-400 mt-1">Estimated XP</div>
                  </div>
                </div>
                <div className="rounded border border-slate-700 bg-slate-900/60 px-3 py-2 text-xs text-slate-300">
                  XP breakdown: {missionXpEstimate.checkSuccesses} successful checks x 50 = {missionXpEstimate.checkXp};
                  scene bonuses = {missionXpEstimate.sceneXp}; combat = {missionXpEstimate.combatXp};
                  extra challenge XP = {missionXpEstimate.bonusXp}.
                </div>
                {(isGM || activeMission.scenes[activeMission.scenes.length - 1]?.outcome) && (
                  <div>
                    <div className="mb-1 text-xs font-semibold uppercase tracking-wide text-slate-400">Outcome Notes</div>
                    {isGM ? (
                      <div className="flex gap-2">
                        <textarea
                          value={outcomeNotes}
                          onChange={e => setOutcomeNotes(e.target.value)}
                          placeholder="Record what the Kill-team decided, what was achieved, what was lost..."
                          rows={3}
                          className="flex-1 rounded border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-slate-100 placeholder-slate-500"
                        />
                        <button onClick={saveOutcomeNotes} disabled={saving} className="self-end rounded bg-emerald-700 px-3 py-2 text-sm hover:bg-emerald-600 disabled:opacity-50">
                          Save
                        </button>
                      </div>
                    ) : (
                      <div className="rounded border border-slate-700 bg-slate-900/60 px-3 py-2 text-sm italic text-slate-200">
                        {activeMission.scenes[activeMission.scenes.length - 1]?.outcome || ''}
                      </div>
                    )}
                  </div>
                )}
              </div>
            )}
          </div>
        )}

        {/* GM mission selector */}
        {isGM && (
          <div className="rounded-lg border border-blue-500/30 bg-blue-950/20 p-4">
            <div className="mb-3 flex items-center justify-between gap-3">
              <div>
                <h2 className="text-lg font-semibold">GM Mission Control</h2>
                <p className="text-sm text-blue-100/70">Choose a story from the database and make it the mission everyone plays from this tab.</p>
              </div>
              <span className="rounded bg-slate-900/70 px-2 py-1 text-xs text-blue-200">{missions.length} saved</span>
            </div>
            <div className="flex flex-col gap-2 md:flex-row">
              <select
                className="min-w-0 flex-1 rounded border border-slate-600 bg-slate-900 px-3 py-2"
                value={selectedMissionId}
                onChange={e => setSelectedMissionId(e.target.value)}
              >
                <option value="">Select mission from database</option>
                {missions.map(mission => (
                  <option key={mission.id} value={mission.id}>
                    {mission.name} ({missionTheme(mission)}, {mission.threat_level || 'Medium'})
                  </option>
                ))}
              </select>
              <button
                onClick={() => setMissionActive()}
                disabled={!selectedMissionId || saving}
                className="rounded bg-blue-600 px-4 py-2 font-medium hover:bg-blue-500 disabled:opacity-50"
              >
                Set Active Mission
              </button>
            </div>
          </div>
        )}

        {loading ? (
          <div className="rounded-lg border border-slate-700 bg-slate-900 p-6 text-slate-300">Loading mission data...</div>
        ) : !activeMission ? (
          <div className="rounded-lg border border-slate-700 bg-slate-900 p-6">
            <h2 className="text-xl font-semibold">No Active Mission</h2>
            <p className="mt-2 text-slate-300">
              {isGM ? 'Select a saved mission above to begin play.' : 'The GM has not selected a mission yet.'}
            </p>
          </div>
        ) : (
          <>
            <div className={isGM ? 'grid gap-4 lg:grid-cols-[1.5fr_1fr]' : 'grid gap-4'}>

              {/* ── Left: scene content ── */}
              <div className="rounded-lg border border-slate-700 bg-slate-900 p-5">
                <div className="mb-4 flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
                  <div>
                    <h2 className="text-2xl font-bold">{activeMission.name}</h2>
                    {isGM && (
                      <p className="mt-1 text-sm capitalize text-slate-300">
                        {missionTheme(activeMission)} threat · {activeMission.threat_level || 'Medium'} · {activeMission.player_count || '?'} battle-brothers
                      </p>
                    )}
                  </div>
                  {isGM && (
                    <div className="rounded bg-blue-600/20 px-3 py-2 text-sm text-blue-100">
                      Scene {activeMission.current_scene + 1} / {activeMission.scenes.length || 1}
                    </div>
                  )}
                </div>

                {currentScene ? (
                  <div className="space-y-4">

                    {/* ── Scene briefing ── */}
                    <div className="rounded-lg border border-blue-500/30 bg-blue-950/20 p-4">
                      <div className="mb-2 flex items-center justify-between gap-3">
                        <div>
                          <p className="text-xs uppercase tracking-wide text-blue-300">
                            {isGM ? 'Current Scene' : 'Mission Briefing'}
                          </p>
                          {/* Title gated for players */}
                          {isGM || sceneReveal.title ? (
                            <h3 className="text-xl font-semibold">{titleForScene(currentScene, activeMission.current_scene)}</h3>
                          ) : (
                            <h3 className="text-xl font-semibold text-slate-500">Scene {activeMission.current_scene + 1}</h3>
                          )}
                        </div>
                        {isGM && (
                          <span className="rounded bg-slate-950/80 px-2 py-1 text-xs text-slate-300">{currentScene.type || 'Scene'}</span>
                        )}
                      </div>

                      {/* Per-element reveal gates for players */}
                      {isGM ? (
                        /* GM always sees everything */
                        <div className="space-y-3">
                          {currentScene.mood && (
                            <div className="rounded border border-amber-500/20 bg-amber-950/20 px-4 py-3">
                              <p className="text-sm italic leading-relaxed text-amber-100">{currentScene.mood}</p>
                            </div>
                          )}
                          <p className="whitespace-pre-wrap text-slate-200">{textForScene(currentScene) || 'No scene text stored.'}</p>
                          {sceneObjectives.length > 0 && (
                            <div>
                              <h4 className="mb-2 text-sm font-semibold uppercase tracking-wide text-blue-200">Objectives</h4>
                              <div className="grid gap-2">
                                {sceneObjectives.map((obj, i) => (
                                  <div key={i} className="rounded border border-slate-700 bg-slate-950/40 px-3 py-2 text-sm text-slate-200">{obj}</div>
                                ))}
                              </div>
                            </div>
                          )}
                          {sceneComplications.length > 0 && (
                            <div>
                              <h4 className="mb-2 text-sm font-semibold uppercase tracking-wide text-amber-200">Pressure</h4>
                              <div className="grid gap-2">
                                {sceneComplications.map((comp, i) => (
                                  <div key={i} className="rounded border border-amber-500/30 bg-amber-950/20 px-3 py-2 text-sm text-amber-100">{comp}</div>
                                ))}
                              </div>
                            </div>
                          )}
                        </div>
                      ) : (
                        /* Players see only what GM has unlocked */
                        (() => {
                          const anyVisible = sceneReveal.mood || sceneReveal.description || sceneReveal.objectives || sceneReveal.complications;
                          if (!anyVisible) {
                            return (
                              <div className="flex flex-col items-center justify-center gap-3 py-6 text-center">
                                <div className="text-2xl">🔒</div>
                                <p className="text-sm text-slate-400">Awaiting GM scene briefing.<br />Stand by, Kampbroder.</p>
                              </div>
                            );
                          }
                          return (
                            <div className="space-y-3">
                              {sceneReveal.mood && currentScene.mood && (
                                <div className="rounded border border-amber-500/20 bg-amber-950/20 px-4 py-3">
                                  <p className="text-sm italic leading-relaxed text-amber-100">{currentScene.mood}</p>
                                </div>
                              )}
                              {sceneReveal.description && (
                                <p className="whitespace-pre-wrap text-slate-200">{textForScene(currentScene)}</p>
                              )}
                              {sceneReveal.objectives && sceneObjectives.length > 0 && (
                                <div>
                                  <h4 className="mb-2 text-sm font-semibold uppercase tracking-wide text-blue-200">Objectives</h4>
                                  <div className="grid gap-2">
                                    {sceneObjectives.map((obj, i) => (
                                      <div key={i} className="rounded border border-slate-700 bg-slate-950/40 px-3 py-2 text-sm text-slate-200">{obj}</div>
                                    ))}
                                  </div>
                                </div>
                              )}
                              {sceneReveal.complications && sceneComplications.length > 0 && (
                                <div>
                                  <h4 className="mb-2 text-sm font-semibold uppercase tracking-wide text-amber-200">Pressure</h4>
                                  <div className="grid gap-2">
                                    {sceneComplications.map((comp, i) => (
                                      <div key={i} className="rounded border border-amber-500/30 bg-amber-950/20 px-3 py-2 text-sm text-amber-100">{comp}</div>
                                    ))}
                                  </div>
                                </div>
                              )}
                            </div>
                          );
                        })()
                      )}

                        {isGM && currentScene.completed && (
                          <div className="mt-3 rounded border border-emerald-500/30 bg-emerald-950/30 px-3 py-2 text-sm text-emerald-200">
                            <div className="font-semibold">Scene completed</div>
                            {endingTextForScene(currentScene) && (
                              <p className="mt-2 whitespace-pre-wrap text-emerald-100">{endingTextForScene(currentScene)}</p>
                            )}
                          </div>
                        )}
	                      </div>

	                      {sceneExtraChallenges.length > 0 && (isGM || sceneReveal.complications || sceneExtraChallenges.some(c => c.completed)) && (
                        <div className="rounded-lg border border-amber-500/30 bg-amber-950/10 p-4">
                          <div className="mb-3 flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
                            <div>
                              <h4 className="text-sm font-semibold uppercase tracking-wide text-amber-200">Extra Challenges</h4>
                              <p className="text-xs text-slate-400">
                                Earned here becomes mission reward. Heroic work grants Renown; Reserve Req is only a mission resource.
                              </p>
                            </div>
                            <div className="text-xs text-amber-100">
                              {sceneExtraChallenges.filter(c => c.completed).length} / {sceneExtraChallenges.length} solved
                            </div>
                          </div>
                          <div className="grid gap-2">
                            {sceneExtraChallenges.map((challenge, index) => (
                              <div
                                key={`${challenge.id}-${index}`}
                                className={`rounded border px-3 py-2 text-sm ${
                                  challenge.completed
                                    ? 'border-emerald-500/40 bg-emerald-950/30 text-emerald-100'
                                    : 'border-slate-700 bg-slate-950/40 text-slate-200'
                                }`}
                              >
                                <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                                  <div className="min-w-0">
                                    <div className="font-medium">{challenge.text}</div>
                                    <div className="mt-0.5 text-xs text-slate-400">Reward: {rewardLabel(challenge)}</div>
                                  </div>
                                  <div className="flex shrink-0 items-center gap-2">
                                    <span className={`rounded px-2 py-1 text-xs ${challenge.completed ? 'bg-emerald-800/70 text-emerald-100' : 'bg-slate-800 text-slate-400'}`}>
                                      {challenge.completed ? 'Earned' : 'Available'}
                                    </span>
                                    {isGM && (
                                      <button
                                        onClick={() => toggleExtraChallenge(index)}
                                        className={`rounded px-2 py-1 text-xs ${
                                          challenge.completed
                                            ? 'bg-slate-700 text-slate-200 hover:bg-slate-600'
                                            : 'bg-amber-700 text-white hover:bg-amber-600'
                                        }`}
                                      >
                                        {challenge.completed ? 'Undo' : 'Mark Solved'}
                                      </button>
                                    )}
                                  </div>
                                </div>
                              </div>
                            ))}
                          </div>
                        </div>
                      )}

                      {/* ── Checks + Combat grid ── */}
                      <div className="grid gap-4 lg:grid-cols-[1.1fr_0.9fr]">

                      {/* Scene Checks */}
                      <div className="rounded-lg border border-slate-700 bg-slate-950/40 p-4">
                        <div className="mb-3">
                          <h4 className="text-sm font-semibold uppercase tracking-wide text-slate-300">Scene Checks</h4>
                        </div>

                        {/* Player: active check banner */}
                        {!isGM && playPrompt?.type === 'check' && playPrompt.check && (() => {
                          const check = playPrompt.check;
                          const assignedTo = playPrompt.assignedTo;
                          const isDecision = playPrompt.isDecision;
                          const isAssignedToMe = !assignedTo || assignedTo === authedPlayer;
                          const hasRolled = rolledChecks.has(check.name);
                          const canFate = hasRolled && !fateUsedChecks.has(check.name) && !isDecision;
                          return (
                            <div className="mb-4 rounded border border-emerald-500/30 bg-emerald-950/30 p-3 text-sm">
                              <div className="font-semibold text-emerald-200">{playPrompt.title}</div>
                              {assignedTo && (
                                <div className="mt-0.5 text-xs text-emerald-100/60">
                                  Assigned to <span className="capitalize font-medium text-emerald-200">{assignedTo}</span>
                                </div>
                              )}
                              <div className="mt-1 text-emerald-100/80">
                                {isDecision ? 'Declare your intent for this moment.' : 'Roll this check when your Battle-Brother acts.'}
                              </div>

                              {isAssignedToMe && (
                                isDecision ? (
                                  <div className="mt-3 space-y-2">
                                    <textarea
                                      value={declarationText}
                                      onChange={e => setDeclarationText(e.target.value)}
                                      placeholder="State your Battle-Brother's decision..."
                                      rows={2}
                                      className="w-full rounded border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-slate-100 placeholder-slate-500"
                                    />
                                    <button
                                      onClick={rollDecision}
                                      disabled={!declarationText.trim() || hasRolled}
                                      className="rounded bg-emerald-700 px-3 py-2 text-sm hover:bg-emerald-600 disabled:cursor-not-allowed disabled:opacity-40"
                                    >
                                      {hasRolled ? 'Declared' : 'Declare'}
                                    </button>
                                  </div>
                                ) : (
                                  <div className="mt-3 flex flex-wrap gap-2">
                                    <button
                                      onClick={() => rollSceneCheck(check)}
                                      disabled={hasRolled && !currentScene?.rollsUnlocked}
                                      title={hasRolled && !currentScene?.rollsUnlocked ? 'Already rolled this scene' : undefined}
                                      className="rounded bg-emerald-700 px-3 py-2 text-sm hover:bg-emerald-600 disabled:cursor-not-allowed disabled:opacity-40"
                                    >
                                      {hasRolled ? 'Already Rolled' : 'Roll Check'}
                                    </button>
                                    {/* #9 — fate point re-roll */}
                                    {canFate && (
                                      <button
                                        onClick={() => spendFate(check.name)}
                                        className="rounded border border-amber-600/50 bg-amber-950/30 px-3 py-2 text-xs text-amber-300 hover:bg-amber-900/30"
                                        title="Spend a Fate point to re-roll this check"
                                      >
                                        Spend Fate ✦
                                      </button>
                                    )}
                                  </div>
                                )
                              )}
                              {!isAssignedToMe && (
                                <div className="mt-2 text-xs text-slate-400 italic">Not assigned to you.</div>
                              )}
                              {/* #5 — reward only shown after rolling */}
                              {hasRolled && check.reward && (
                                <div className="mt-3 rounded border border-slate-700 bg-slate-900/60 px-3 py-2 text-xs text-slate-300">
                                  <span className="text-amber-300 font-semibold">Reward: </span>{check.reward}
                                </div>
                              )}
                            </div>
                          );
                        })()}

                        {/* Player: visible scene checks (only GM-revealed ones) */}
                        {!isGM && (() => {
                          const revealedChecks = sceneChecks.filter(c => sceneReveal.checks[c.name]);
                          if (revealedChecks.length === 0) {
                            return (
                              <div className="flex flex-col items-center justify-center gap-3 py-6 text-center">
                                <div className="text-2xl">📋</div>
                                <p className="text-sm text-slate-400">No check open yet.<br />Awaiting GM orders.</p>
                              </div>
                            );
                          }
                          return (
                            <div className="grid gap-3">
                              {revealedChecks.map((check, index) => {
                                const target = clampTarget((check.target || 45) + (check.modifier || 0));
                                return (
                                  <div key={`${check.name}-${index}`} className="rounded border border-slate-700 bg-slate-900 p-3">
                                    <div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
                                      <div className="min-w-0">
                                        <div className="font-semibold text-slate-100">{check.name}</div>
                                        <div className="text-xs text-slate-400">
                                          {check.skill || 'Skill'} ({check.characteristic || 'Characteristic'}) target {target}
                                          {check.modifier ? ` (${check.modifier > 0 ? '+' : ''}${check.modifier})` : ''}
                                        </div>
                                        {check.rulesNote && (
                                          <div className="mt-1 text-xs text-slate-500">{check.rulesNote}</div>
                                        )}
                                        {check.reward && (
                                          <div className="mt-1 text-xs text-amber-200">Success: {check.reward}</div>
                                        )}
                                      </div>
                                      <button
                                        onClick={openSceneInRoller}
                                        className="rounded bg-emerald-700 px-3 py-2 text-sm font-medium hover:bg-emerald-600"
                                      >
                                        Open in Dice Roller
                                      </button>
                                    </div>
                                  </div>
                                );
                              })}
                            </div>
                          );
                        })()}

                        {/* GM: full check list */}
                        {isGM && (
                          <div className="grid gap-3">
                            {sceneChecks.map((check, index) => {
                              const target = clampTarget((check.target || 45) + (check.modifier || 0));
                              return (
                                <div key={`${check.name}-${index}`} className="rounded border border-slate-700 bg-slate-900 p-3">
                                  <div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
                                    <div>
                                      <div className="font-semibold text-slate-100">{check.name}</div>
                                      <div className="text-xs text-slate-400">
                                        {check.skill || 'Skill'} ({check.characteristic || 'Characteristic'}) target {target}
                                        {check.modifier ? ` (${check.modifier > 0 ? '+' : ''}${check.modifier})` : ''}
                                      </div>
                                    </div>
                                    <button
                                      onClick={() => rollSceneCheck(check)}
                                      className="rounded bg-blue-600 px-3 py-2 text-sm hover:bg-blue-500"
                                    >
                                      Roll Check
                                    </button>
                                  </div>
                                  {check.reward && <p className="mt-2 text-sm text-slate-300">{check.reward}</p>}
                                </div>
                              );
                            })}
                          </div>
                        )}

                        {/* Recent rolls (all players) */}
                        {rollResults.length > 0 && (
                          <div className="mt-4 rounded border border-slate-700 bg-slate-900 p-3">
                            <h5 className="mb-2 text-sm font-semibold text-slate-200">Recent Scene Rolls</h5>
                            <div className="space-y-2 text-sm">
                              {rollResults.map(result => (
                                <div key={result.id} className="border-b border-slate-800 pb-2 last:border-b-0 last:pb-0">
                                  <div className="flex flex-wrap items-center justify-between gap-2">
                                    <span className="font-medium text-blue-200">{result.rolledBy || 'Unknown'}</span>
                                    <span>
                                      Roll <strong>{result.roll}</strong> vs {result.target} ·{' '}
                                      {result.success
                                        ? <span className="text-emerald-300">Success, {result.dos} DoS</span>
                                        : <span className="text-rose-300">Fail, {result.dof} DoF</span>}
                                    </span>
                                  </div>
                                  <div className="text-xs text-slate-400">{result.name} · {result.skill}</div>
                                </div>
                              ))}
                            </div>
                          </div>
                        )}
                      </div>

                      {/* Combat Setup */}
                      <div className="rounded-lg border border-slate-700 bg-slate-950/40 p-4">
                        <h4 className="mb-3 text-sm font-semibold uppercase tracking-wide text-slate-300">Combat Setup</h4>
                        {!isGM && !sceneReveal.combat ? (
                          <div className="flex flex-col items-center justify-center gap-3 py-6 text-center">
                            <div className="text-2xl">⚔️</div>
                            <p className="text-sm text-slate-400">Combat briefing er klassificeret.<br />Afventer GM-ordre.</p>
                          </div>
                        ) : (
                          <>
                            <div className="space-y-2 text-sm">
                              <div className="flex justify-between gap-3 border-b border-slate-800 pb-2">
                                <span className="text-slate-400">Enemy</span>
                                <span className="text-right">{sceneCombat.enemy || 'Custom/None'}</span>
                              </div>
                              <div className="flex justify-between gap-3 border-b border-slate-800 pb-2">
                                <span className="text-slate-400">Attack target</span>
                                <span>BS {sceneCombat.bs || 45} / WS {sceneCombat.ws || 45}</span>
                              </div>
                              <div className="flex justify-between gap-3 border-b border-slate-800 pb-2">
                                <span className="text-slate-400">Modifier</span>
                                <span>{Number(sceneCombat.modifier || 0) >= 0 ? '+' : ''}{sceneCombat.modifier || 0}</span>
                              </div>
                              <div className="flex justify-between gap-3 border-b border-slate-800 pb-2">
                                <span className="text-slate-400">Damage</span>
                                <span>{sceneCombat.damage || '1d10+5'}</span>
                              </div>
                              <div className="flex justify-between gap-3">
                                <span className="text-slate-400">Target soak</span>
                                <span>TB {sceneCombat.targetTB || 4} / AR {sceneCombat.targetArmour || 5}</span>
                              </div>
                            </div>
                            <button
                              onClick={openSceneInRoller}
                              className="mt-4 w-full rounded bg-emerald-700 px-3 py-2 text-sm font-medium hover:bg-emerald-600"
                            >
                              Open in Dice Roller
                            </button>
                          </>
                        )}
                        {isGM && (
                          <div className="mt-2 grid grid-cols-2 gap-2">
                            <button onClick={openCombatForPlayers} className="rounded bg-blue-700 px-3 py-2 text-sm hover:bg-blue-600">Open Combat</button>
                            <button onClick={clearPlayPrompt} className="rounded bg-slate-700 px-3 py-2 text-sm hover:bg-slate-600">Clear Open</button>
                          </div>
                        )}
                      </div>
                    </div>

                    {/* ── Combat state panels (initiative, conditions, action ref, fear) ── */}
                    {(combatActive || (!isGM && sceneReveal.combat)) && (
                      <div className="mt-4 space-y-4">

                        {/* Round counter + fear rating */}
                        <div className="rounded-lg border border-red-900/50 bg-red-950/20 p-4">
                          <div className="flex flex-wrap items-center gap-4">
                            <div className="flex items-center gap-3">
                              <span className="text-sm font-semibold uppercase tracking-wide text-red-300">Round</span>
                              {isGM && <button onClick={() => shiftRound(-1)} disabled={combatState.round <= 1} className="rounded bg-slate-700 px-2 py-1 text-sm hover:bg-slate-600 disabled:opacity-40">−</button>}
                              <span className="text-2xl font-bold tabular-nums">{combatState.round}</span>
                              {isGM && <button onClick={() => shiftRound(1)} className="rounded bg-slate-700 px-2 py-1 text-sm hover:bg-slate-600">+</button>}
                            </div>
                            <div className="flex min-w-0 flex-wrap items-center gap-2 text-xs">
                              <span className="rounded bg-slate-900/60 px-2 py-1 text-slate-300">
                                Current: <span className="font-semibold text-slate-100">{combatCurrentTurn ? turnLabel(combatCurrentTurn) : 'None'}</span>
                              </span>
                              <span className="rounded bg-slate-900/60 px-2 py-1 text-slate-300">
                                Next: <span className="font-semibold text-slate-100">{combatNextTurn ? turnLabel(combatNextTurn) : 'None'}</span>
                              </span>
                            </div>
                            {isGM ? (
                              <div className="flex items-center gap-2">
                                <span className="text-sm text-slate-400">Fear:</span>
                                <select
                                  value={combatState.fearRating}
                                  onChange={e => setFearRating(Number(e.target.value))}
                                  className="rounded border border-slate-600 bg-slate-900 px-2 py-1 text-sm"
                                >
                                  {FEAR_RATINGS.map(f => <option key={f.value} value={f.value}>{f.label}</option>)}
                                </select>
                              </div>
                            ) : combatState.fearRating > 0 ? (
                              <div className="rounded border border-rose-700/40 bg-rose-950/30 px-3 py-1 text-sm">
                                <span className="font-semibold text-rose-300">{FEAR_RATINGS[combatState.fearRating]?.label}</span>
                                {FEAR_RATINGS[combatState.fearRating]?.penalty && (
                                  <span className="ml-2 text-rose-200/70">{FEAR_RATINGS[combatState.fearRating].penalty}</span>
                                )}
                              </div>
                            ) : null}
                            {isGM && sortedCombatInitiatives.length > 0 && (
                              <div className="ml-auto flex flex-wrap items-center gap-2">
                                <button onClick={() => advanceCombatTurn(-1)} className="rounded border border-slate-600 bg-slate-800 px-2 py-1 text-xs text-slate-200 hover:bg-slate-700">
                                  Prev turn
                                </button>
                                <button onClick={() => advanceCombatTurn(1)} className="rounded bg-cyan-700 px-2 py-1 text-xs text-white hover:bg-cyan-600">
                                  Next turn
                                </button>
                              </div>
                            )}
                            {isGM && (
                              <button onClick={clearCombatState} className="rounded bg-slate-700 px-2 py-1 text-xs text-slate-300 hover:bg-slate-600">
                                Reset Combat
                              </button>
                            )}
                          </div>
                        </div>

                        {/* #7 — Fear test quick-roll (Finding #2: penalty auto-applied) */}
                        {combatState.fearRating > 0 && (
                          <div className="rounded-lg border border-rose-900/40 bg-rose-950/10 p-4">
                            <h4 className="mb-3 text-sm font-semibold uppercase tracking-wide text-rose-300">Fear Test Required</h4>
                            <div className="flex flex-wrap items-end gap-3">
                              <div>
                                <label className="mb-1 block text-xs text-slate-400">
                                  Willpower{fearPenaltyValue !== 0 && (
                                    <span className="ml-1 text-rose-300">({fearPenaltyValue} penalty → effective {effectiveWP})</span>
                                  )}
                                </label>
                                <input
                                  type="number"
                                  min="1" max="100"
                                  value={wpTarget}
                                  onChange={e => setWpTarget(e.target.value)}
                                  className="w-20 rounded border border-slate-600 bg-slate-900 px-3 py-2 text-sm"
                                />
                              </div>
                              <button
                                onClick={rollFearTest}
                                disabled={rolledChecks.has('__fear__')}
                                className="rounded bg-rose-700 px-4 py-2 text-sm font-medium hover:bg-rose-600 disabled:cursor-not-allowed disabled:opacity-40"
                              >
                                {rolledChecks.has('__fear__') ? 'Fear Test Rolled' : `Roll Fear Test (WP ${effectiveWP})`}
                              </button>
                              {rolledChecks.has('__fear__') && !fateUsedChecks.has('__fear__') && (
                                <button
                                  onClick={() => spendFate('__fear__')}
                                  className="rounded border border-amber-600/50 bg-amber-950/30 px-3 py-2 text-xs text-amber-300 hover:bg-amber-900/30"
                                  title="Spend a Fate point to re-roll"
                                >
                                  Spend Fate ✦
                                </button>
                              )}
                            </div>
                          </div>
                        )}

                        {/* Initiative tracker */}
                        <div className="rounded-lg border border-slate-700 bg-slate-950/40 p-4">
                          <h4 className="mb-3 text-sm font-semibold uppercase tracking-wide text-slate-300">Initiative Order</h4>

                          {/* Player roll form */}
                          {!myInitiative && (
                            <div className="mb-4 flex flex-wrap items-end gap-3">
                              <div>
                                <label className="mb-1 block text-xs text-slate-400">Agility Bonus</label>
                                <input
                                  type="number" min="0" max="20"
                                  value={agBonus}
                                  onChange={e => setAgBonus(e.target.value)}
                                  placeholder="e.g. 4"
                                  className="w-24 rounded border border-slate-600 bg-slate-900 px-3 py-2 text-sm"
                                />
                              </div>
                              <button
                                onClick={rollInitiative}
                                disabled={saving}
                                className="rounded bg-blue-600 px-4 py-2 text-sm font-medium hover:bg-blue-500 disabled:opacity-50"
                              >
                                Roll Initiative (1d10 + {parseInt(agBonus) || 0})
                              </button>
                            </div>
                          )}
                          {myInitiative && !isGM && (
                            <div className="mb-3 text-sm text-slate-400">
                              Your initiative: <strong className="text-slate-100">{myInitiative.total}</strong> (d10:{myInitiative.dieRoll} + Ab:{myInitiative.agBonus})
                              <button
                                onClick={() => { setAgBonus(String(myInitiative.agBonus)); updateCombatState({ initiatives: combatState.initiatives.filter(e => e.player !== authedPlayer) }); }}
                                className="ml-3 text-xs text-slate-500 hover:text-slate-300"
                              >
                                Re-roll
                              </button>
                            </div>
                          )}

                          {/* #3 — GM NPC form */}
                          {isGM && (
                            <div className="mb-4 flex flex-wrap items-end gap-2 rounded border border-slate-700 bg-slate-900/40 p-3">
                              <div>
                                <label className="mb-1 block text-xs text-slate-400">NPC / Enemy Name</label>
                                <input
                                  type="text"
                                  value={npcName}
                                  onChange={e => setNpcName(e.target.value)}
                                  placeholder="e.g. Hormagaunt"
                                  className="rounded border border-slate-600 bg-slate-900 px-3 py-2 text-sm"
                                  onKeyDown={e => e.key === 'Enter' && addNpcInitiative()}
                                />
                              </div>
                              <div>
                                <label className="mb-1 block text-xs text-slate-400">Roll (blank = auto d10)</label>
                                <input
                                  type="number" min="1" max="20"
                                  value={npcRoll}
                                  onChange={e => setNpcRoll(e.target.value)}
                                  placeholder="1–20"
                                  className="w-20 rounded border border-slate-600 bg-slate-900 px-3 py-2 text-sm"
                                />
                              </div>
                              <button
                                onClick={addNpcInitiative}
                                disabled={!npcName.trim()}
                                className="rounded bg-red-800 px-3 py-2 text-sm hover:bg-red-700 disabled:opacity-40"
                              >
                                Add NPC
                              </button>
                            </div>
                          )}

                          {/* Initiative list */}
                          {combatState.initiatives.length === 0 ? (
                            <p className="text-sm text-slate-500">No initiatives submitted yet.</p>
                          ) : (
                            <div className="space-y-1">
                              {sortedCombatInitiatives.map((entry, i) => {
                                const key = initiativeKey(entry);
                                const isCurrentTurn = combatTurnIndex >= 0 ? key === initiativeKey(combatCurrentTurn) : i === 0;
                                const isNextTurn = combatNextTurn ? key === initiativeKey(combatNextTurn) : false;
                                return (
                                <div
                                  key={key || `${entry.player}-${i}`}
                                  className={`flex items-center gap-3 rounded px-3 py-2 text-sm ${isCurrentTurn ? 'ring-1 ring-cyan-400/80' : ''} ${
                                    entry.type === 'npc'
                                      ? 'bg-red-950/30 border border-red-900/40'
                                      : entry.player === authedPlayer
                                        ? 'bg-blue-950/40 border border-blue-800/40'
                                        : 'bg-slate-900/60'
                                  }`}
                                >
                                  <span className="w-5 text-center font-bold text-slate-400">{i + 1}</span>
                                  {isCurrentTurn && <span className="rounded bg-cyan-700 px-1.5 py-0.5 text-[11px] font-semibold text-white">Now</span>}
                                  {!isCurrentTurn && isNextTurn && <span className="rounded bg-slate-700 px-1.5 py-0.5 text-[11px] font-semibold text-slate-200">Next</span>}
                                  {entry.type === 'npc' && <span className="rounded bg-red-900/60 px-1.5 py-0.5 text-xs text-red-300">NPC</span>}
                                  <span className="flex-1 font-medium capitalize">{entry.player}</span>
                                  <span className="text-lg font-bold tabular-nums">{entry.total}</span>
                                  {entry.type !== 'npc' && <span className="text-xs text-slate-500">d10:{entry.dieRoll} + Ab:{entry.agBonus}</span>}
                                  {isGM && (
                                    <button
                                      onClick={() => updateCombatState({ initiatives: combatState.initiatives.filter(e => !(e.player === entry.player && e.ts === entry.ts)) })}
                                      className="rounded bg-slate-800 px-2 py-0.5 text-xs text-slate-400 hover:bg-slate-700"
                                    >×</button>
                                  )}
                                </div>
                              );
                              })}
                            </div>
                          )}
                        </div>

                        {/* Status conditions */}
                        {(isGM || combatEntities.some(e => (combatState.conditions[e.name] || []).length > 0)) && (
                          <div className="rounded-lg border border-slate-700 bg-slate-950/40 p-4">
                            <h4 className="mb-3 text-sm font-semibold uppercase tracking-wide text-slate-300">Conditions</h4>
                            {combatEntities.length === 0 ? (
                              <p className="text-sm text-slate-500">Roll initiative to populate combatants.</p>
                            ) : (
                              <div className="space-y-3">
                                {combatEntities.map(entity => {
                                  const active = combatState.conditions[entity.name] || [];
                                  if (!isGM && active.length === 0) return null;
                                  return (
                                    <div key={entity.name} className="rounded border border-slate-800 bg-slate-900/60 p-3">
                                      <div className="mb-2 flex items-center gap-2">
                                        <span className={`text-xs px-1.5 py-0.5 rounded ${entity.type === 'player' ? 'bg-blue-900/50 text-blue-300' : 'bg-red-900/50 text-red-300'}`}>
                                          {entity.type === 'player' ? 'Battle-Brother' : entity.type === 'npc' ? 'NPC' : 'Enemy'}
                                        </span>
                                        <span className="font-medium capitalize">{entity.name}</span>
                                      </div>
                                      <div className="flex flex-wrap gap-1.5">
                                        {CONDITIONS.map(cond => {
                                          const on = active.includes(cond);
                                          return isGM ? (
                                            <button
                                              key={cond}
                                              onClick={() => toggleCondition(entity.name, cond)}
                                              className={`rounded px-2 py-1 text-xs transition-colors ${on ? 'bg-rose-700 text-white' : 'bg-slate-800 text-slate-400 hover:bg-slate-700'}`}
                                            >
                                              {cond}
                                            </button>
                                          ) : on ? (
                                            <span key={cond} className="rounded bg-rose-700/80 px-2 py-1 text-xs text-white">{cond}</span>
                                          ) : null;
                                        })}
                                      </div>
                                    </div>
                                  );
                                })}
                              </div>
                            )}
                          </div>
                        )}

                        {/* Action economy reference */}
                        <div className="rounded-lg border border-slate-700 bg-slate-950/40 p-4">
                          <button
                            onClick={() => setShowActionRef(v => !v)}
                            className="flex w-full items-center justify-between text-sm font-semibold uppercase tracking-wide text-slate-300"
                          >
                            Action Economy Reference
                            <span className="text-slate-500">{showActionRef ? '▲' : '▼'}</span>
                          </button>
                          {showActionRef && (
                            <div className="mt-3 grid gap-2 sm:grid-cols-2">
                              {ACTION_ECONOMY.map(row => (
                                <div key={row.type} className={`rounded border ${row.border} ${row.bg} px-3 py-2`}>
                                  <div className={`mb-1 text-xs font-bold uppercase tracking-wide ${row.color}`}>{row.type}</div>
                                  <div className="text-xs text-slate-300">{row.examples}</div>
                                </div>
                              ))}
                              <div className="rounded border border-slate-700 bg-slate-900/30 px-3 py-2 sm:col-span-2">
                                <div className="mb-1 text-xs font-bold uppercase tracking-wide text-slate-400">Each Turn</div>
                                <div className="text-xs text-slate-300">2 Half Actions <em>or</em> 1 Full Action · 1 Reaction · Unlimited Free Actions</div>
                              </div>
                            </div>
                          )}
                        </div>
                      </div>
                    )}

                    {/* #1 — player roll feed (current scene) */}
                    {!isGM && playerRollFeed.length > 0 && (
                      <div className="rounded-lg border border-slate-700 bg-slate-900 p-4">
                        <h4 className="mb-3 text-sm font-semibold uppercase tracking-wide text-slate-300">Scene Roll Feed</h4>
                        <div className="max-h-80 space-y-2 overflow-y-auto pr-1">
                          {playerRollFeed.map(roll => (
                            <RollFeedRow key={roll.id} roll={roll} showDelete={false} />
                          ))}
                        </div>
                      </div>
                    )}

                  </div>
                ) : (
                  <div className="rounded-lg border border-slate-700 bg-slate-800 p-4 text-slate-300">This mission has no scenes stored.</div>
                )}

                {/* GM scene navigation */}
                {isGM && currentScene && (
                  <div className="mt-4 flex flex-wrap gap-2">
                    <button
                      onClick={() => saveProgress(activeMission.current_scene - 1)}
                      disabled={saving || activeMission.current_scene <= 0}
                      className="rounded bg-slate-700 px-3 py-2 text-sm hover:bg-slate-600 disabled:opacity-50"
                    >
                      Previous Scene
                    </button>
                    <button
                      onClick={() => toggleSceneComplete(activeMission.current_scene)}
                      disabled={saving}
                      className="rounded bg-emerald-700 px-3 py-2 text-sm hover:bg-emerald-600 disabled:opacity-50"
                    >
                      {currentScene.completed ? 'Reopen Scene' : 'Mark Scene Complete'}
                    </button>
                    <button
                      onClick={() => saveProgress(activeMission.current_scene + 1)}
                      disabled={saving || activeMission.current_scene >= activeMission.scenes.length - 1}
                      className="rounded bg-blue-600 px-3 py-2 text-sm hover:bg-blue-500 disabled:opacity-50"
                    >
                      Advance Scene
                    </button>
                  </div>
                )}
              </div>

              {/* ── Right: GM Scene Secrets + Table State ── */}
              {isGM && (
                <div className="space-y-4">

                  {/* Scene Secrets */}
                  <div className="rounded-lg border border-amber-500/30 bg-amber-950/10 p-5">
                    <div className="mb-4 flex items-center justify-between gap-2">
                      <h3 className="text-lg font-semibold text-amber-200">Scene Secrets</h3>
                      <div className="flex gap-1.5">
                        <button
                          onClick={() => editingScene ? setEditingScene(false) : openSceneEdit()}
                          className={`rounded px-2 py-1 text-xs ${editingScene ? 'bg-amber-700 text-amber-100 hover:bg-amber-600' : 'bg-slate-700 text-slate-300 hover:bg-slate-600'}`}
                        >
                          {editingScene ? 'Close Editor' : '✏ Edit Scene'}
                        </button>
                        {playPrompt && (
                          <button onClick={clearPlayPrompt} className="rounded bg-slate-700 px-2 py-1 text-xs text-slate-300 hover:bg-slate-600">
                            Close All
                          </button>
                        )}
                      </div>
                    </div>

                    {/* #2 — per-element reveal controls */}
                    <div className="mb-4 rounded border border-slate-700 bg-slate-900/60 p-3">
                      <div className="mb-2 flex items-center justify-between">
                        <div className="text-xs font-semibold uppercase tracking-wide text-slate-400">Reveal to Players</div>
                        <div className="flex gap-1">
                          <button onClick={revealAll} className="rounded bg-emerald-900/60 px-2 py-0.5 text-xs text-emerald-300 hover:bg-emerald-800/60">All</button>
                          <button onClick={lockAll} className="rounded bg-slate-800 px-2 py-0.5 text-xs text-slate-400 hover:bg-slate-700">Lock</button>
                        </div>
                      </div>
                      <div className="grid grid-cols-2 gap-1 text-xs">
                        {[
                          { key: 'title', label: 'Title' },
                          { key: 'mood', label: 'Mood' },
                          { key: 'description', label: 'Description' },
                          { key: 'objectives', label: 'Objectives' },
                          { key: 'complications', label: 'Complications' },
                        ].map(({ key, label }) => (
                          <button
                            key={key}
                            onClick={() => toggleReveal(key)}
                            className={`flex items-center justify-between gap-1 rounded px-2 py-1 text-left ${sceneReveal[key] ? 'bg-emerald-900/50 text-emerald-300' : 'bg-slate-800 text-slate-400'}`}
                          >
                            <span>{label}</span>
                            <span>{sceneReveal[key] ? '👁' : '🔒'}</span>
                          </button>
                        ))}
                      </div>
                    </div>

                    {/* Currently revealed */}
                    <div className="mb-4 rounded border border-slate-700 bg-slate-950/50 p-3 text-sm">
                      {playPrompt ? (
                        <div className="flex items-start justify-between gap-2">
                          <div>
                            <div className="mb-0.5 text-xs uppercase tracking-wide text-slate-400">Revealed to players</div>
                            <div className="font-semibold text-emerald-200">{playPrompt.title || playPrompt.type}</div>
                            {playPrompt.type === 'check' && playPrompt.check && (
                              <div className="mt-0.5 text-xs text-slate-400">
                                {playPrompt.check.skill} ({playPrompt.check.characteristic}) · {playPrompt.check.target}
                                {playPrompt.assignedTo && ` · Assigned: ${playPrompt.assignedTo}`}
                                {playPrompt.isDecision && ' · Decision'}
                              </div>
                            )}
                            {playPrompt.type === 'combat' && (
                              <div className="mt-0.5 text-xs text-slate-400">Initiative order · Combat stats</div>
                            )}
                          </div>
                          <button onClick={clearPlayPrompt} className="shrink-0 rounded bg-rose-900/60 px-2 py-1 text-xs text-rose-300 hover:bg-rose-800/60">
                            Close
                          </button>
                        </div>
                      ) : (
                        <div className="text-slate-500">Nothing revealed to players yet.</div>
                      )}
                    </div>

                    {/* Checks — #6 assignment + #8 decision options */}
                    <div className="mb-4">
                      <div className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-400">Checks</div>
                      <div className="space-y-1.5">
                        {sceneChecks.map((check) => {
                          const target = clampTarget((check.target || 45) + (check.modifier || 0));
                          const isOpen = playPrompt?.type === 'check' && playPrompt.title === check.name;
                          const isExpanded = checkOpenOptions?.name === check.name;
                          const isCheckRevealed = !!sceneReveal.checks[check.name];
                          const isManualEntry = gmManualRoll?.checkName === check.name;
                          return (
                            <div key={check.name} className={`rounded border ${isOpen ? 'border-emerald-700/60 bg-emerald-950/30' : 'border-slate-700 bg-slate-900/60'}`}>
                              <div className="flex items-center gap-2 px-3 py-2">
                                <div className="min-w-0 flex-1">
                                  <div className="truncate text-sm font-medium">{check.name}</div>
                                  <div className="text-xs text-slate-400">{check.skill} ({check.characteristic}) · {target}</div>
                                </div>
                                {/* Reveal toggle */}
                                <button
                                  onClick={() => toggleRevealCheck(check.name)}
                                  title={isCheckRevealed ? 'Hide from players' : 'Reveal to players'}
                                  className={`shrink-0 rounded px-2 py-1 text-xs ${isCheckRevealed ? 'bg-emerald-900/50 text-emerald-300 hover:bg-emerald-800/50' : 'bg-slate-800 text-slate-500 hover:bg-slate-700'}`}
                                >
                                  {isCheckRevealed ? '👁' : '🔒'}
                                </button>
                                {/* Manual roll entry toggle */}
                                <button
                                  onClick={() => setGmManualRoll(isManualEntry ? null : { checkName: check.name, playerName: playerList[0] || '', value: '' })}
                                  title="Enter a physical dice result"
                                  className={`shrink-0 rounded px-2 py-1 text-xs ${isManualEntry ? 'bg-blue-900/60 text-blue-300' : 'bg-slate-800 text-slate-400 hover:bg-slate-700'}`}
                                >
                                  🎲
                                </button>
                                {isOpen ? (
                                  <button
                                    onClick={clearPlayPrompt}
                                    className="shrink-0 rounded bg-emerald-700 px-2 py-1 text-xs text-white hover:bg-emerald-600"
                                  >
                                    Opened ✓
                                  </button>
                                ) : (
                                  <>
                                    <button
                                      onClick={() => openCheckForPlayers(check, checkOpenOptions?.name === check.name ? { assignedTo: checkOpenOptions.assignedTo, isDecision: checkOpenOptions.isDecision } : {})}
                                      className="shrink-0 rounded bg-slate-700 px-2 py-1 text-xs text-slate-300 hover:bg-slate-600"
                                    >
                                      Open
                                    </button>
                                    <button
                                      onClick={() => setCheckOpenOptions(isExpanded ? null : { name: check.name, assignedTo: '', isDecision: false })}
                                      className="shrink-0 rounded bg-slate-800 px-2 py-1 text-xs text-slate-400 hover:bg-slate-700"
                                      title="Options"
                                    >
                                      ⚙
                                    </button>
                                  </>
                                )}
                              </div>

                              {/* Manual roll entry form */}
                              {isManualEntry && (
                                <div className="border-t border-blue-900/40 bg-blue-950/20 px-3 py-2 space-y-2">
                                  <div className="text-xs font-semibold text-blue-300">Enter physical dice result</div>
                                  <div className="flex items-center gap-2">
                                    <label className="text-xs text-slate-400 w-16 shrink-0">Player:</label>
                                    <select
                                      value={gmManualRoll.playerName}
                                      onChange={e => setGmManualRoll(prev => ({ ...prev, playerName: e.target.value }))}
                                      className="flex-1 rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs"
                                    >
                                      <option value="">— select —</option>
                                      {playerList.map(p => <option key={p} value={p}>{p}</option>)}
                                    </select>
                                  </div>
                                  <div className="flex items-center gap-2">
                                    <label className="text-xs text-slate-400 w-16 shrink-0">Roll (1–100):</label>
                                    <input
                                      type="number"
                                      min="1"
                                      max="100"
                                      value={gmManualRoll.value}
                                      onChange={e => setGmManualRoll(prev => ({ ...prev, value: e.target.value }))}
                                      placeholder="e.g. 42"
                                      className="w-24 rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs"
                                    />
                                    {gmManualRoll.value && !isNaN(parseInt(gmManualRoll.value)) && (
                                      <span className={`text-xs font-semibold ${parseInt(gmManualRoll.value) <= target ? 'text-emerald-300' : 'text-rose-300'}`}>
                                        {parseInt(gmManualRoll.value) <= target ? 'Success' : 'Fail'}
                                      </span>
                                    )}
                                  </div>
                                  <div className="flex gap-2">
                                    <button
                                      onClick={submitGmManualRoll}
                                      disabled={!gmManualRoll.playerName || !gmManualRoll.value}
                                      className="rounded bg-blue-700 px-3 py-1 text-xs text-white hover:bg-blue-600 disabled:opacity-40"
                                    >
                                      Submit Roll
                                    </button>
                                    <button
                                      onClick={() => setGmManualRoll(null)}
                                      className="rounded bg-slate-700 px-3 py-1 text-xs text-slate-300 hover:bg-slate-600"
                                    >
                                      Cancel
                                    </button>
                                  </div>
                                </div>
                              )}

                              {/* #6 + #8 — expanded options */}
                              {isExpanded && !isOpen && (
                                <div className="border-t border-slate-700 px-3 py-2 space-y-2 bg-slate-950/30">
                                  <div className="flex items-center gap-2">
                                    <label className="text-xs text-slate-400 w-20">Assign to:</label>
                                    <select
                                      value={checkOpenOptions.assignedTo}
                                      onChange={e => setCheckOpenOptions(prev => ({ ...prev, assignedTo: e.target.value }))}
                                      className="flex-1 rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs"
                                    >
                                      <option value="">Anyone</option>
                                      {playerList.map(p => <option key={p} value={p}>{p}</option>)}
                                    </select>
                                  </div>
                                  <label className="flex items-center gap-2 text-xs text-slate-400 cursor-pointer">
                                    <input
                                      type="checkbox"
                                      checked={checkOpenOptions.isDecision}
                                      onChange={e => setCheckOpenOptions(prev => ({ ...prev, isDecision: e.target.checked }))}
                                      className="rounded"
                                    />
                                    Decision (no dice — declare intent)
                                  </label>
                                  <button
                                    onClick={() => openCheckForPlayers(check, { assignedTo: checkOpenOptions.assignedTo, isDecision: checkOpenOptions.isDecision })}
                                    className="w-full rounded bg-emerald-800 px-2 py-1 text-xs text-emerald-200 hover:bg-emerald-700"
                                  >
                                    Open with these options
                                  </button>
                                </div>
                              )}
                            </div>
                          );
                        })}
                      </div>
                    </div>

                    {/* Combat */}
                    <div className="mb-4">
                      <div className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-400">Combat</div>
                      <div className={`flex items-center gap-2 rounded border px-3 py-2 ${combatActive ? 'border-red-700/60 bg-red-950/20' : 'border-slate-700 bg-slate-900/60'}`}>
                        <div className="min-w-0 flex-1">
                          <div className="text-sm font-medium">{sceneCombat.enemy || 'Custom/None'}</div>
                          <div className="text-xs text-slate-400">BS {sceneCombat.bs || 45} / WS {sceneCombat.ws || 45} · {sceneCombat.damage || '1d10+5'}</div>
                        </div>
                        {/* Reveal toggle */}
                        <button
                          onClick={() => toggleReveal('combat')}
                          title={sceneReveal.combat ? 'Hide combat from players' : 'Reveal combat to players'}
                          className={`shrink-0 rounded px-2 py-1 text-xs ${sceneReveal.combat ? 'bg-emerald-900/50 text-emerald-300 hover:bg-emerald-800/50' : 'bg-slate-800 text-slate-500 hover:bg-slate-700'}`}
                        >
                          {sceneReveal.combat ? '👁' : '🔒'}
                        </button>
                        <button
                          onClick={combatActive ? clearPlayPrompt : openCombatForPlayers}
                          className={`shrink-0 rounded px-2 py-1 text-xs ${combatActive ? 'bg-rose-800 text-rose-200 hover:bg-rose-700' : 'bg-blue-700 text-white hover:bg-blue-600'}`}
                        >
                          {combatActive ? 'Close' : 'Open Combat'}
                        </button>
                      </div>
                    </div>

                    {/* Roll limit */}
                    <div className="flex items-center justify-between rounded border border-slate-700 bg-slate-900/60 px-3 py-2 text-sm">
                      <span className="text-slate-300">Player roll limit</span>
                      <button
                        onClick={toggleSceneRollsUnlocked}
                        className={`rounded px-2 py-1 text-xs ${currentScene?.rollsUnlocked ? 'bg-amber-700 hover:bg-amber-600' : 'bg-slate-700 hover:bg-slate-600'}`}
                      >
                        {currentScene?.rollsUnlocked ? 'Unlimited (lock)' : '1 per scene (unlock)'}
                      </button>
                    </div>
                  </div>

                  {/* Table State */}
                  <div className="rounded-lg border border-slate-700 bg-slate-900 p-5">
                    <h3 className="mb-3 text-lg font-semibold">Table State</h3>
                    <div className="space-y-3 text-sm">
                      <div className="flex justify-between border-b border-slate-700 pb-2">
                        <span className="text-slate-400">GM</span>
                        <span>{activeMission.gm_player || 'Unassigned'}</span>
                      </div>
                      <div className="flex justify-between border-b border-slate-700 pb-2">
                        <span className="text-slate-400">Scenes complete</span>
                        <span>{activeMission.scenes.filter(s => s.completed).length} / {activeMission.scenes.length}</span>
                      </div>
                        <div className="flex justify-between border-b border-slate-700 pb-2">
                          <span className="text-slate-400">Enemy pressure</span>
                          <span>{activeMission.enemy_count || '?'} per scene</span>
                        </div>
                        <div className="flex justify-between border-b border-slate-700 pb-2">
                          <span className="text-slate-400">Extra rewards</span>
                          <span className="text-right">
                            {earnedMissionRewards.completed} / {earnedMissionRewards.total} solved
                            {earnedMissionRewards.renown ? ` · +${earnedMissionRewards.renown} Renown` : ''}
                            {earnedMissionRewards.reserveReq ? ` · +${earnedMissionRewards.reserveReq} Reserve Req` : ''}
                            {earnedMissionRewards.xp ? ` · +${earnedMissionRewards.xp} XP` : ''}
                          </span>
                        </div>
                        <div className="flex justify-between border-b border-slate-700 pb-2">
                          <span className="text-slate-400">Estimated XP</span>
                          <span className="text-right">
                            {missionXpEstimate.total}
                            <span className="ml-1 text-xs text-slate-500">
                              ({missionXpEstimate.checkXp} checks, {missionXpEstimate.sceneXp} scenes, {missionXpEstimate.combatXp} combat)
                            </span>
                          </span>
                        </div>
                        <div className="flex justify-between">
                          <span className="text-slate-400">Last update</span>
                          <span>{activeMission.updated_at ? new Date(activeMission.updated_at).toLocaleString('da-DK') : 'Unknown'}</span>
                        </div>
                    </div>
                  </div>

                </div>
              )}

              {currentScene && (isGM || Object.values(sceneReveal.checks || {}).some(Boolean) || sceneReveal.combat || currentScene?.rollsUnlocked) && (
                <div className="lg:col-span-2">
                  {renderMiniSceneRoller(currentScene, activeMission.current_scene)}
                </div>
              )}

              {/* #1 — GM Roll Feed (full width under table state) */}
              {isGM && (
                <div className="lg:col-span-2 rounded-lg border border-slate-700 bg-slate-900 p-5">
                  <div className="mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                    <h3 className="text-lg font-semibold">Roll Feed</h3>
                    <div className="flex flex-wrap gap-2">
                      <button onClick={loadRollFeed} className="rounded bg-slate-700 px-3 py-2 text-sm hover:bg-slate-600">
                        Refresh Rolls
                      </button>
                      {rollFeed.length > 0 && (
                        <button onClick={clearRollFeed} className="rounded bg-rose-900/70 px-3 py-2 text-sm text-rose-100 hover:bg-rose-800">
                          Clear Feed
                        </button>
                      )}
                    </div>
                  </div>
                  {rollFeed.length === 0 ? (
                    <div className="text-sm text-slate-400">No mission rolls recorded yet.</div>
                  ) : (
                    <div className="grid max-h-96 gap-2 overflow-y-auto pr-1 lg:grid-cols-2">
                      {rollFeed.map(roll => <RollFeedRow key={roll.id} roll={roll} showDelete onDelete={deleteRoll} />)}
                    </div>
                  )}
                </div>
              )}
            </div>

            {/* Scene Editor — full width, below the two-column grid */}
            {isGM && editingScene && sceneEdit && (
              <div className="w-full min-w-0 rounded-lg border border-amber-600/40 bg-amber-950/10 p-4 sm:p-5 space-y-5 overflow-hidden">
                <div className="flex items-center justify-between gap-2">
                  <h3 className="text-lg font-semibold text-amber-200">Edit Scene — {sceneEdit.title || 'Untitled'}</h3>
                  <div className="flex gap-2">
                    <button
                      onClick={saveSceneEdit}
                      disabled={saving}
                      className="rounded bg-amber-700 px-3 py-1.5 text-sm font-medium hover:bg-amber-600 disabled:opacity-50"
                    >
                      {saving ? 'Saving…' : 'Save Changes'}
                    </button>
                    <button
                      onClick={() => { setEditingScene(false); setSceneEdit(null); }}
                      className="rounded bg-slate-700 px-3 py-1.5 text-sm hover:bg-slate-600"
                    >
                      Cancel
                    </button>
                  </div>
                </div>

                {/* Title + Type + Description — 3-col on wide screens */}
                <div className="grid min-w-0 gap-4 lg:grid-cols-[minmax(0,1fr)_auto_minmax(0,2fr)]">
                  <div className="min-w-0">
                    <label className="mb-1 block text-xs text-slate-400">Scene Title</label>
                    <input
                      type="text"
                      value={sceneEdit.title}
                      onChange={e => seField('title', e.target.value)}
                      className="w-full min-w-0 rounded border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-slate-100"
                    />
                  </div>
                  <div className="min-w-0">
                    <label className="mb-1 block text-xs text-slate-400">Type</label>
                    <select
                      value={sceneEdit.type}
                      onChange={e => seField('type', e.target.value)}
                      className="w-full rounded border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-slate-100"
                    >
                      {['intro','investigation','combat','social','puzzle','boss'].map(t => (
                        <option key={t} value={t}>{t}</option>
                      ))}
                    </select>
                  </div>
                  <div className="min-w-0">
                    <label className="mb-1 block text-xs text-slate-400">Mood / Read-aloud <span className="text-slate-500 normal-case">(vist i kursiv til spillerne)</span></label>
                    <textarea
                      value={sceneEdit.mood || ''}
                      onChange={e => seField('mood', e.target.value)}
                      rows={2}
                      placeholder="Kortfattet stemningssætter…"
                      className="w-full rounded border border-amber-800/40 bg-amber-950/10 px-3 py-2 text-sm italic text-amber-100 placeholder-slate-600"
                    />
                  </div>
                </div>

                <div>
                  <label className="mb-1 block text-xs text-slate-400">Scene Description / Briefing</label>
                  <textarea
                    value={sceneEdit.description}
                    onChange={e => seField('description', e.target.value)}
                    rows={4}
                    className="w-full rounded border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-slate-100"
                  />
                </div>

                <div>
                  <label className="mb-1 block text-xs text-slate-400">Ending Text / Scene Complete Read-aloud</label>
                  <textarea
                    value={sceneEdit.endingText || ''}
                    onChange={e => seField('endingText', e.target.value)}
                    rows={3}
                    placeholder="Tekst GM læser op når scenen er færdig…"
                    className="w-full rounded border border-emerald-700/40 bg-emerald-950/10 px-3 py-2 text-sm text-emerald-100 placeholder-slate-600"
                  />
                </div>

                {/* Checks + Enemies — side by side on wide screens */}
                <div className="grid min-w-0 gap-4 xl:grid-cols-2">

                  {/* Checks */}
                  <div className="min-w-0">
                    <div className="mb-2 flex items-center justify-between">
                      <label className="text-xs font-semibold uppercase tracking-wide text-slate-400">Checks</label>
                      <button onClick={addCheck} className="rounded bg-slate-700 px-2 py-1 text-xs hover:bg-slate-600">+ Add</button>
                    </div>
                    <div className="space-y-3">
                      {sceneEdit.checks.map((check, i) => (
                        <div key={i} className="min-w-0 rounded border border-slate-700 bg-slate-900/60 p-3 space-y-2">
                          <div className="flex gap-2">
                            <input
                              type="text"
                              value={check.name}
                              onChange={e => seCheck(i, 'name', e.target.value)}
                              placeholder="Check name"
                              className="min-w-0 flex-1 rounded border border-slate-600 bg-slate-900 px-2 py-1.5 text-sm text-slate-100"
                            />
                            <button onClick={() => removeCheck(i)} className="shrink-0 rounded bg-rose-900/50 px-2 py-1 text-xs text-rose-300 hover:bg-rose-800/60">×</button>
                          </div>
                          <div className="grid min-w-0 grid-cols-2 gap-2 sm:grid-cols-4">
                            <div>
                              <label className="mb-0.5 block text-xs text-slate-500">Skill</label>
                              <input type="text" value={check.skill || ''} onChange={e => seCheck(i, 'skill', e.target.value)} className="w-full rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs text-slate-100" />
                            </div>
                            <div>
                              <label className="mb-0.5 block text-xs text-slate-500">Char</label>
                              <input type="text" value={check.characteristic || ''} onChange={e => seCheck(i, 'characteristic', e.target.value)} className="w-full rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs text-slate-100" />
                            </div>
                            <div>
                              <label className="mb-0.5 block text-xs text-slate-500">Target</label>
                              <input type="number" value={check.target || 45} onChange={e => seCheck(i, 'target', Number(e.target.value))} className="w-full rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs text-slate-100" />
                            </div>
                            <div>
                              <label className="mb-0.5 block text-xs text-slate-500">Mod</label>
                              <input type="number" value={check.modifier || 0} onChange={e => seCheck(i, 'modifier', Number(e.target.value))} className="w-full rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs text-slate-100" />
                            </div>
                          </div>
                          <div>
                            <label className="mb-0.5 block text-xs text-slate-500">Reward (shown after roll)</label>
                            <input type="text" value={check.reward || ''} onChange={e => seCheck(i, 'reward', e.target.value)} placeholder="What succeeding unlocks…" className="w-full rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs text-slate-100" />
                          </div>
                        </div>
                      ))}
                      {sceneEdit.checks.length === 0 && (
                        <p className="text-xs text-slate-500">No checks — defaults generated from scene type.</p>
                      )}
                    </div>
                  </div>

                  {/* Enemies */}
                  <div className="min-w-0">
                    <div className="mb-2 flex items-center justify-between">
                      <label className="text-xs font-semibold uppercase tracking-wide text-slate-400">Enemies</label>
                      <button onClick={addEnemy} className="rounded bg-slate-700 px-2 py-1 text-xs hover:bg-slate-600">+ Add</button>
                    </div>
                    <div className="space-y-3">
                      {sceneEdit.enemies.map((enemy, i) => (
                        <div key={i} className="min-w-0 rounded border border-slate-700 bg-slate-900/60 p-3 space-y-2">
                          <div className="flex gap-2">
                            <input
                              type="text"
                              value={enemy.name || ''}
                              onChange={e => seEnemy(i, 'name', e.target.value)}
                              placeholder="Enemy name"
                              className="min-w-0 flex-1 rounded border border-slate-600 bg-slate-900 px-2 py-1.5 text-sm text-slate-100"
                            />
                            <button onClick={() => removeEnemy(i)} className="shrink-0 rounded bg-rose-900/50 px-2 py-1 text-xs text-rose-300 hover:bg-rose-800/60">×</button>
                          </div>
                          <div className="grid min-w-0 grid-cols-2 gap-2 sm:grid-cols-3 xl:grid-cols-5">
                            <div>
                              <label className="mb-0.5 block text-xs text-slate-500">Wounds</label>
                              <input type="number" value={enemy.wounds || 10} onChange={e => seEnemy(i, 'wounds', Number(e.target.value))} className="w-full rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs text-slate-100" />
                            </div>
                            <div>
                              <label className="mb-0.5 block text-xs text-slate-500">BS</label>
                              <input type="number" value={enemy.bs || 45} onChange={e => seEnemy(i, 'bs', Number(e.target.value))} className="w-full rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs text-slate-100" />
                            </div>
                            <div>
                              <label className="mb-0.5 block text-xs text-slate-500">WS</label>
                              <input type="number" value={enemy.ws || 45} onChange={e => seEnemy(i, 'ws', Number(e.target.value))} className="w-full rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs text-slate-100" />
                            </div>
                            <div>
                              <label className="mb-0.5 block text-xs text-slate-500">Ag</label>
                              <input type="number" value={enemy.ag || 30} onChange={e => seEnemy(i, 'ag', Number(e.target.value))} className="w-full rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs text-slate-100" />
                            </div>
                            <div>
                              <label className="mb-0.5 block text-xs text-slate-500">Damage</label>
                              <input type="text" value={enemy.damage || '1d10+4'} onChange={e => seEnemy(i, 'damage', e.target.value)} className="w-full rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs text-slate-100" />
                            </div>
                            <div>
                              <label className="mb-0.5 block text-xs text-slate-500">AP</label>
                              <input type="number" value={enemy.ap || 0} onChange={e => seEnemy(i, 'ap', Number(e.target.value))} className="w-full rounded border border-slate-600 bg-slate-900 px-2 py-1 text-xs text-slate-100" />
                            </div>
                          </div>
                        </div>
                      ))}
                      {sceneEdit.enemies.length === 0 && (
                        <p className="text-xs text-slate-500">No enemies — defaults generated from scene index.</p>
                      )}
                    </div>
                  </div>
                </div>

                {/* Objectives + Complications — side by side */}
                <div className="grid min-w-0 gap-4 lg:grid-cols-2">
                  <div className="min-w-0">
                    <label className="mb-1 block text-xs text-slate-400">Objectives (one per line)</label>
                    <textarea
                      value={sceneEdit.objectives}
                      onChange={e => seField('objectives', e.target.value)}
                      rows={3}
                      placeholder="Secure the perimeter&#10;Locate the gene-code terminal&#10;…"
                      className="w-full rounded border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-slate-100 placeholder-slate-600"
                    />
                  </div>
                  <div className="min-w-0">
                    <label className="mb-1 block text-xs text-slate-400">Complications / Pressure (one per line)</label>
                    <textarea
                      value={sceneEdit.complications}
                      onChange={e => seField('complications', e.target.value)}
                      rows={3}
                      placeholder="Spore mines trigger on failed stealth&#10;Reinforcements arrive round 3&#10;…"
                      className="w-full rounded border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-slate-100 placeholder-slate-600"
                    />
                  </div>
                </div>

                <button
                  onClick={saveSceneEdit}
                  disabled={saving}
                  className="w-full rounded bg-amber-700 px-3 py-2 text-sm font-medium hover:bg-amber-600 disabled:opacity-50"
                >
                  {saving ? 'Saving…' : 'Save Changes'}
                </button>
              </div>
            )}

            {/* GM Mission Scenes overview */}
            {isGM && (
              <div className="rounded-lg border border-slate-700 bg-slate-900 p-5">
                <h3 className="mb-3 text-lg font-semibold">Mission Scenes</h3>
                <div className="grid gap-3">
                  {activeMission.scenes.map((scene, index) => {
                    const active = index === activeMission.current_scene;
                    const enemies = sceneEnemies(scene);
                    return (
                      <div
                        key={scene.id || index}
                        className={`rounded-lg border p-4 ${active ? 'border-blue-500 bg-blue-950/20' : 'border-slate-700 bg-slate-800/70'}`}
                      >
                        <div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
                          <div>
                            <div className="flex flex-wrap items-center gap-2">
                              <span className="rounded bg-slate-950 px-2 py-1 text-xs text-slate-300">Scene {index + 1}</span>
                              {active && <span className="rounded bg-blue-600 px-2 py-1 text-xs">Current</span>}
                              {scene.completed && <span className="rounded bg-emerald-700 px-2 py-1 text-xs">Complete</span>}
                              {scene.revealed === false && <span className="rounded bg-slate-700 px-2 py-1 text-xs text-slate-400">Hidden</span>}
                            </div>
                            <h4 className="mt-2 text-lg font-semibold">{titleForScene(scene, index)}</h4>
                            <p className="mt-1 whitespace-pre-wrap text-sm text-slate-300">{textForScene(scene) || 'No story text stored.'}</p>
	                            {enemies.length > 0 && (
	                              <p className="mt-2 text-xs text-amber-200">Enemies: {enemies.join(', ')}</p>
	                            )}
	                          </div>
                          <div className="flex shrink-0 gap-2">
                            <button
                              onClick={() => setCurrentScene(index)}
                              disabled={saving || active}
                              className="rounded bg-slate-700 px-3 py-2 text-xs hover:bg-slate-600 disabled:opacity-50"
                            >
                              Make Current
                            </button>
                            <button
                              onClick={() => toggleSceneComplete(index)}
                              disabled={saving}
                              className="rounded bg-emerald-700 px-3 py-2 text-xs hover:bg-emerald-600 disabled:opacity-50"
                            >
                              {scene.completed ? 'Reopen' : 'Complete'}
	                            </button>
	                          </div>
	                        </div>
	                        {renderMiniSceneRoller(scene, index, { compact: true })}
	                      </div>
	                    );
                  })}
                </div>
              </div>
            )}

            {/* GM-only full story for read-aloud prep */}
            {isGM && (
              <div className="rounded-lg border border-amber-600/30 bg-slate-900 p-5">
                <div className="mb-4 flex flex-col gap-1 sm:flex-row sm:items-start sm:justify-between">
                  <div>
                    <p className="text-xs uppercase tracking-wide text-amber-300">GM Only</p>
                    <h3 className="text-lg font-semibold text-amber-100">Hele historien til oplæsning</h3>
                  </div>
                  <span className="rounded bg-slate-950 px-2 py-1 text-xs text-slate-400">
                    {activeMission.scenes.length} scenes
                  </span>
                </div>
                <div className="grid gap-4">
                  {activeMission.scenes.map((scene, index) => {
                    const objectives = listForScene(scene, 'objectives');
                    const complications = listForScene(scene, 'complications');
                    const challenges = extraChallengesForScene(scene);
                    return (
                      <article key={scene.id || index} className="rounded border border-slate-700 bg-slate-950/40 p-4">
                        <div className="mb-3 flex flex-wrap items-center gap-2">
                          <span className="rounded bg-amber-900/40 px-2 py-1 text-xs text-amber-200">Scene {index + 1}</span>
                          <span className="rounded bg-slate-800 px-2 py-1 text-xs text-slate-300">{scene.type || 'scene'}</span>
                          {index === activeMission.current_scene && <span className="rounded bg-blue-700 px-2 py-1 text-xs text-blue-100">Current</span>}
                          {scene.completed && <span className="rounded bg-emerald-700 px-2 py-1 text-xs text-emerald-100">Complete</span>}
                        </div>
                        <h4 className="text-xl font-semibold text-slate-100">{titleForScene(scene, index)}</h4>
                        {scene.mood && (
                          <div className="mt-3 rounded border border-amber-500/20 bg-amber-950/20 px-4 py-3">
                            <p className="whitespace-pre-wrap text-sm italic leading-relaxed text-amber-100">{scene.mood}</p>
                          </div>
                        )}
                        <p className="mt-3 whitespace-pre-wrap text-sm leading-relaxed text-slate-200">
                          {textForScene(scene) || 'No scene text stored.'}
                        </p>
                        {endingTextForScene(scene) && (
                          <div className="mt-4 rounded border border-emerald-500/20 bg-emerald-950/20 px-4 py-3">
                            <h5 className="mb-2 text-xs font-semibold uppercase tracking-wide text-emerald-200">Ending Read-aloud</h5>
                            <p className="whitespace-pre-wrap text-sm leading-relaxed text-emerald-100">{endingTextForScene(scene)}</p>
                          </div>
                        )}
                        {(objectives.length > 0 || complications.length > 0 || challenges.length > 0) && (
                          <div className="mt-4 grid gap-3 lg:grid-cols-3">
                            {objectives.length > 0 && (
                              <div>
                                <h5 className="mb-2 text-xs font-semibold uppercase tracking-wide text-blue-200">Objectives</h5>
                                <ul className="space-y-1 text-sm text-slate-300">
                                  {objectives.map((objective, objectiveIndex) => <li key={objectiveIndex}>{objective}</li>)}
                                </ul>
                              </div>
                            )}
                            {complications.length > 0 && (
                              <div>
                                <h5 className="mb-2 text-xs font-semibold uppercase tracking-wide text-amber-200">Pressure</h5>
                                <ul className="space-y-1 text-sm text-slate-300">
                                  {complications.map((complication, complicationIndex) => <li key={complicationIndex}>{complication}</li>)}
                                </ul>
                              </div>
                            )}
                            {challenges.length > 0 && (
                              <div>
                                <h5 className="mb-2 text-xs font-semibold uppercase tracking-wide text-emerald-200">Extra Rewards</h5>
                                <ul className="space-y-1 text-sm text-slate-300">
                                  {challenges.map((challenge, challengeIndex) => (
                                    <li key={`${challenge.id}-${challengeIndex}`}>
                                      {challenge.text} <span className="text-slate-500">({rewardLabel(challenge)})</span>
                                    </li>
                                  ))}
                                </ul>
                              </div>
                            )}
                          </div>
                        )}
                      </article>
                    );
                  })}
                </div>
              </div>
            )}
          </>
        )}
      </div>
    </section>
  );
}
