#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import shutil
import subprocess
import time
from pathlib import Path
from urllib.parse import urlencode, urljoin

from verify_mobile_browser_controls import (
    WebDriverError,
    execute_js,
    free_port,
    request_json,
    wait_for_driver,
    wait_for_map_runtime,
    wait_for_page,
)

ROOT = Path(__file__).resolve().parents[1]


def load_map(base: str, port: int, session_id: str, params: dict[str, str]) -> str:
    query = urlencode({**params, "_": str(time.time_ns())})
    url = urljoin(base.rstrip("/") + "/", f"/web/game.html?{query}")
    request_json(port, "POST", f"/session/{session_id}/url", {"url": url}, timeout=30)
    wait_for_page(port, session_id)
    wait_for_map_runtime(port, session_id, params["map"])
    return url


def start_dialogue_completion_script() -> str:
    return """
window.__hwanseCandidateDialogueCompletion = null;
window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_LOG = [];
window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_RENDER = [];
window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK = null;
window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK_RENDER = null;
if (typeof activeDialogueCompletionFeedbacks !== 'undefined') activeDialogueCompletionFeedbacks = [];
window.HWANSE_SOUND_COUNTS = {};
window.HWANSE_SOUND_LOG = [];
window.HWANSE_LAST_SOUND = null;
window.HWANSE_DIALOGUE_OPEN_SOUND_LOG = [];
window.HWANSE_LAST_DIALOGUE_OPEN_SOUND = null;
window.HWANSE_DIALOGUE_ADVANCE_SOUND_LOG = [];
window.HWANSE_LAST_DIALOGUE_ADVANCE_SOUND = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
const captureDialogueSoundState = () => ({
  counts: { ...(window.HWANSE_SOUND_COUNTS || {}) },
  last: window.HWANSE_LAST_SOUND || null,
  log: Array.isArray(window.HWANSE_SOUND_LOG) ? [...window.HWANSE_SOUND_LOG] : [],
  openLog: Array.isArray(window.HWANSE_DIALOGUE_OPEN_SOUND_LOG)
    ? [...window.HWANSE_DIALOGUE_OPEN_SOUND_LOG]
    : [],
  openLast: window.HWANSE_LAST_DIALOGUE_OPEN_SOUND || null,
  advanceLog: Array.isArray(window.HWANSE_DIALOGUE_ADVANCE_SOUND_LOG)
    ? [...window.HWANSE_DIALOGUE_ADVANCE_SOUND_LOG]
    : [],
  advanceLast: window.HWANSE_LAST_DIALOGUE_ADVANCE_SOUND || null,
});
ensureEventDialogueBlocks()
  .then(() => {
    updateDialogueButton();
    const button = document.getElementById('dialogueButton');
    render();
    const beforePrompt = window.HWANSE_LAST_ACTION_PROMPT || null;
    const promptActivated = activateHotspotAtFoot();
    const promptActivation = {
      activated: promptActivated,
      blockId: activeDialogue?.block?.blockId || '',
      lineCount: activeDialogue?.lines?.length || 0,
      prompt: beforePrompt,
      originalEventVmRuntimeImplemented: false,
    };
    activeDialogue = null;
    prototypeProgress = createPrototypeProgressState();
    publishPrototypeProgress();
    updateDialogueButton();
    render();
    window.HWANSE_SOUND_COUNTS = {};
    window.HWANSE_SOUND_LOG = [];
    window.HWANSE_LAST_SOUND = null;
    window.HWANSE_DIALOGUE_OPEN_SOUND_LOG = [];
    window.HWANSE_LAST_DIALOGUE_OPEN_SOUND = null;
    const before = { text: button?.textContent || '', title: button?.title || '' };
	    const started = startDialogueCandidateAction();
	    if (!started) throw new Error('dialogue candidate did not start');
	    const openedId = activeDialogue?.block?.blockId || '';
	    const openedSoundState = captureDialogueSoundState();
	    const cancelButton = document.getElementById('virtualCancelButton');
	    updateVirtualCancelButton();
	    render();
	    const dialogueChromeBefore = window.HWANSE_LAST_DIALOGUE_BOX_RENDER || null;
	    const mobileCancelBefore = {
	      active: !!activeDialogue,
	      blockId: activeDialogue?.block?.blockId || '',
	      index: activeDialogue?.index ?? null,
	      lineCount: activeDialogue?.lines?.length || 0,
	      hidden: !!cancelButton?.hidden,
	      text: cancelButton?.textContent || '',
	      scene,
	      dialogueChrome: dialogueChromeBefore,
	      completeCount: (window.HWANSE_LAST_PROTOTYPE_PROGRESS?.counts || {})['dialogue-complete'] || 0,
	    };
	    cancelButton?.dispatchEvent(new PointerEvent('pointerup', {
	      bubbles: true,
	      cancelable: true,
	      pointerId: 54,
	      pointerType: 'touch',
	      isPrimary: true,
	    }));
	    updateDialogueButton();
	    updateVirtualCancelButton();
	    render();
	    const mobileCancelAfter = {
	      active: !!activeDialogue,
	      blockId: activeDialogue?.block?.blockId || '',
	      index: activeDialogue?.index ?? null,
	      hidden: !!cancelButton?.hidden,
	      text: cancelButton?.textContent || '',
	      scene,
	      completeCount: (window.HWANSE_LAST_PROTOTYPE_PROGRESS?.counts || {})['dialogue-complete'] || 0,
	      actionPrompt: window.HWANSE_LAST_ACTION_PROMPT || null,
	    };
	    const restarted = startDialogueCandidateAction();
	    if (!restarted) throw new Error('dialogue candidate did not restart after mobile cancel');
	    const reopenedId = activeDialogue?.block?.blockId || '';
	    const reopenedSoundState = captureDialogueSoundState();
	    updateVirtualCancelButton();
	    window.HWANSE_SOUND_COUNTS = {};
	    window.HWANSE_SOUND_LOG = [];
	    window.HWANSE_LAST_SOUND = null;
	    window.HWANSE_DIALOGUE_OPEN_SOUND_LOG = [];
	    window.HWANSE_LAST_DIALOGUE_OPEN_SOUND = null;
	    window.HWANSE_DIALOGUE_ADVANCE_SOUND_LOG = [];
	    window.HWANSE_LAST_DIALOGUE_ADVANCE_SOUND = null;
	    const pointerBefore = {
	      blockId: reopenedId,
	      index: activeDialogue?.index ?? null,
	      lineCount: activeDialogue?.lines?.length || 0,
	    };
    const canvas = document.getElementById('screen');
    const rect = canvas.getBoundingClientRect();
	    canvas.dispatchEvent(new PointerEvent('pointerdown', {
	      bubbles: true,
	      cancelable: true,
	      pointerId: 53,
	      pointerType: 'mouse',
      isPrimary: true,
	      clientX: rect.left + rect.width / 2,
	      clientY: rect.top + rect.height - Math.max(20, rect.height * 0.1),
	    }));
	    render();
	    const pointerAfter = {
	      active: !!activeDialogue,
	      blockId: activeDialogue?.block?.blockId || '',
	      index: activeDialogue?.index ?? null,
	      lineCount: activeDialogue?.lines?.length || pointerBefore.lineCount,
	      latch: window.HWANSE_LAST_DIALOGUE_INPUT_WAIT_LATCH || null,
	      dialogueChrome: window.HWANSE_LAST_DIALOGUE_BOX_RENDER || null,
	    };
    const pointerSoundState = captureDialogueSoundState();
    canvas.dispatchEvent(new PointerEvent('pointerup', {
      bubbles: true,
      cancelable: true,
      pointerId: 53,
      pointerType: 'mouse',
      isPrimary: true,
      clientX: rect.left + rect.width / 2,
      clientY: rect.top + rect.height - Math.max(20, rect.height * 0.1),
    }));
    const pointerReleaseAfter = {
      active: !!activeDialogue,
      blockId: activeDialogue?.block?.blockId || '',
      index: activeDialogue?.index ?? null,
      latch: window.HWANSE_LAST_DIALOGUE_INPUT_WAIT_LATCH || null,
    };
    const keyboardLatchBefore = {
      blockId: activeDialogue?.block?.blockId || '',
      index: activeDialogue?.index ?? null,
      lineCount: activeDialogue?.lines?.length || 0,
      latch: window.HWANSE_LAST_DIALOGUE_INPUT_WAIT_LATCH || null,
    };
    window.dispatchEvent(new KeyboardEvent('keydown', {
      bubbles: true,
      cancelable: true,
      code: 'Enter',
      key: 'Enter',
    }));
    const keyboardLatchAfterFirst = {
      active: !!activeDialogue,
      blockId: activeDialogue?.block?.blockId || '',
      index: activeDialogue?.index ?? null,
      latch: window.HWANSE_LAST_DIALOGUE_INPUT_WAIT_LATCH || null,
    };
    const keyboardSoundAfterFirst = captureDialogueSoundState();
    window.dispatchEvent(new KeyboardEvent('keydown', {
      bubbles: true,
      cancelable: true,
      code: 'Enter',
      key: 'Enter',
    }));
    const keyboardLatchAfterRepeat = {
      active: !!activeDialogue,
      blockId: activeDialogue?.block?.blockId || '',
      index: activeDialogue?.index ?? null,
      latch: window.HWANSE_LAST_DIALOGUE_INPUT_WAIT_LATCH || null,
    };
    const keyboardSoundAfterRepeat = captureDialogueSoundState();
    window.dispatchEvent(new KeyboardEvent('keyup', {
      bubbles: true,
      cancelable: true,
      code: 'Enter',
      key: 'Enter',
    }));
    const keyboardLatchAfterRelease = {
      active: !!activeDialogue,
      blockId: activeDialogue?.block?.blockId || '',
      index: activeDialogue?.index ?? null,
      latch: window.HWANSE_LAST_DIALOGUE_INPUT_WAIT_LATCH || null,
    };
    window.dispatchEvent(new KeyboardEvent('keydown', {
      bubbles: true,
      cancelable: true,
      code: 'Enter',
      key: 'Enter',
    }));
    const keyboardLatchAfterSecondAdvance = {
      active: !!activeDialogue,
      blockId: activeDialogue?.block?.blockId || '',
      index: activeDialogue?.index ?? null,
      latch: window.HWANSE_LAST_DIALOGUE_INPUT_WAIT_LATCH || null,
    };
    const keyboardSoundAfterSecondAdvance = captureDialogueSoundState();
    window.dispatchEvent(new KeyboardEvent('keyup', {
      bubbles: true,
      cancelable: true,
      code: 'Enter',
      key: 'Enter',
    }));
    const branchExecution = activeDialogue?.vmReplay?.groundedControlBranchExecution || {};
    const branchRenderLineSet = new Set((branchExecution.renderEvents || [])
      .map((event) => Number(event?.lineIndex))
      .filter((lineIndex) => Number.isFinite(lineIndex)));
    const lineAdvanceOnlyEvent = (branchExecution.lineAdvanceEvents || []).find((event) =>
      Number.isFinite(Number(event?.lineIndex)) &&
      !branchRenderLineSet.has(Number(event.lineIndex)) &&
      Number(event.lineIndex) < (activeDialogue?.lines?.length || 0)
    );
    const branchLineAdvanceProbe = (() => {
      if (!activeDialogue || !lineAdvanceOnlyEvent) return null;
      const restoreIndex = activeDialogue.index;
      activeDialogue.index = Number(lineAdvanceOnlyEvent.lineIndex);
      render();
      const chrome = window.HWANSE_LAST_DIALOGUE_BOX_RENDER || null;
      const renderEvent = chrome?.vmRenderEvent || null;
      const probe = {
        active: !!activeDialogue,
        blockId: activeDialogue?.block?.blockId || '',
        index: activeDialogue?.index ?? null,
        line: activeDialogue?.lines?.[activeDialogue?.index || 0] || '',
        lineAdvanceEvent: lineAdvanceOnlyEvent,
        chrome,
        renderEvent,
      };
      activeDialogue.index = restoreIndex;
      render();
      return probe;
    })();
    const firstBranchWaitEvent = (branchExecution.waitEvents || []).find((event) =>
      Number.isFinite(Number(event?.lineIndex)) &&
      Number(event.lineIndex) < (activeDialogue?.lines?.length || 0)
    );
    const branchWaitProbe = (() => {
      if (!activeDialogue || !firstBranchWaitEvent) return null;
      const restoreIndex = activeDialogue.index;
      activeDialogue.index = Number(firstBranchWaitEvent.lineIndex);
      render();
      const chrome = window.HWANSE_LAST_DIALOGUE_BOX_RENDER || null;
      const latch = publishDialogueInputWaitLatch({ source: 'branch-wait-probe' });
      const probe = {
        active: !!activeDialogue,
        blockId: activeDialogue?.block?.blockId || '',
        index: activeDialogue?.index ?? null,
        line: activeDialogue?.lines?.[activeDialogue?.index || 0] || '',
        waitEvent: firstBranchWaitEvent,
        chrome,
        latch,
      };
      activeDialogue.index = restoreIndex;
      render();
      return probe;
    })();
    let guard = 0;
    while (activeDialogue && guard < 64) {
      advanceDialogue();
      guard += 1;
    }
    updateDialogueButton();
    render();
    const dialogueFeedbackLog = window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_LOG || [];
    const dialogueFeedbackRender = window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_RENDER || [];
    const dialogueFeedbackLast = window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK || null;
    const dialogueFeedbackLastRender = window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK_RENDER || null;
    const dialogueSoundState = captureDialogueSoundState();
    const autoSave = window.HWANSE_LAST_DIALOGUE_COMPLETE_AUTO_SAVE || null;
    const raw = localStorage.getItem(RUNTIME_SAVE_KEY) || '{}';
    const payload = JSON.parse(raw);
    window.__hwanseCandidateDialogueCompletion = {
      saved: autoSave?.saved === true,
      autoSave,
      scene,
	      map: map?.name || '',
	      openedId,
	      mobileCancel: {
	        before: mobileCancelBefore,
	        after: mobileCancelAfter,
		        restarted,
		        reopenedId,
		        openedSoundState,
		        reopenedSoundState,
		      },
      pointerAdvance: {
        before: pointerBefore,
        after: pointerAfter,
        afterRelease: pointerReleaseAfter,
	        advanced: pointerBefore.lineCount <= 1
	          ? pointerAfter.active === false
	          : pointerAfter.blockId === openedId && pointerAfter.index === pointerBefore.index + 1,
	        releaseCleared: pointerReleaseAfter.latch?.active === false,
	        soundState: pointerSoundState,
	      },
	      keyboardInputWaitLatch: {
        before: keyboardLatchBefore,
        afterFirst: keyboardLatchAfterFirst,
        afterRepeat: keyboardLatchAfterRepeat,
        afterRelease: keyboardLatchAfterRelease,
        afterSecondAdvance: keyboardLatchAfterSecondAdvance,
	        firstAdvanced: keyboardLatchAfterFirst.index === keyboardLatchBefore.index + 1,
	        repeatBlocked: keyboardLatchAfterRepeat.index === keyboardLatchAfterFirst.index,
	        releaseCleared: keyboardLatchAfterRelease.latch?.active === false,
	        secondAdvancedAfterRelease: keyboardLatchAfterSecondAdvance.index === keyboardLatchAfterRelease.index + 1,
	        soundAfterFirst: keyboardSoundAfterFirst,
	        soundAfterRepeat: keyboardSoundAfterRepeat,
	        soundAfterSecondAdvance: keyboardSoundAfterSecondAdvance,
      },
      branchLineAdvanceProbe,
      branchWaitProbe,
      nextCandidateId: activeActionDialogueCandidate()?.blockId || '',
      before,
      beforePrompt,
      promptActivation,
	      dialogueFeedbackLog,
	      dialogueFeedbackRender,
	      dialogueFeedbackLast,
	      dialogueFeedbackLastRender,
	      dialogueSoundState,
      actionPrompt: window.HWANSE_LAST_ACTION_PROMPT || null,
      buttonText: button?.textContent || '',
      buttonTitle: button?.title || '',
      completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      storyFlags: window.HWANSE_LAST_PROTOTYPE_STORY_FLAGS || null,
      savedProgress: payload.prototypeProgress || null,
      savedStoryFlags: payload.prototypeStoryFlags || null,
      labels: menuItems().map((item) => menuItemLabel(item)),
    };
  })
  .catch((error) => {
    window.__hwanseCandidateDialogueCompletion = { error: String(error && error.message || error) };
  });
return true;
"""


def dialogue_candidate_menu_selection_script() -> str:
    return """
window.__hwanseDialogueCandidateMenuSelection = null;
window.HWANSE_SOUND_COUNTS = {};
window.HWANSE_SOUND_LOG = [];
window.HWANSE_LAST_SOUND = null;
window.HWANSE_DIALOGUE_OPEN_SOUND_LOG = [];
window.HWANSE_LAST_DIALOGUE_OPEN_SOUND = null;
ensureEventDialogueBlocks()
  .then(() => openDialogueCandidateMenu())
  .then(() => {
    const items = typeof dialogueCandidateMenuItems === 'function' ? dialogueCandidateMenuItems() : [];
    const labels = items.map((item) => item.name || '');
    const targetIndex = items.findIndex((item) => item.dialogueCandidate?.blockId === 'event-dialogue-block-026');
    if (targetIndex < 0) {
      window.__hwanseDialogueCandidateMenuSelection = {
        ok: false,
        error: 'missing event-dialogue-block-026',
        menuMode,
        labels,
        count: items.length,
        menuMarker: window.HWANSE_LAST_DIALOGUE_CANDIDATE_MENU || null,
      };
      return;
    }
    const target = items[targetIndex];
    const before = {
      menuMode,
      notice: menuNotice,
      selectedMenuItemIndex,
      labels,
      count: items.length,
      targetIndex,
      targetName: target.name || '',
      targetBlockId: target.dialogueCandidate?.blockId || '',
      targetPreview: target.preview || '',
      menuMarker: window.HWANSE_LAST_DIALOGUE_CANDIDATE_MENU || null,
    };
    selectedMenuItemIndex = targetIndex;
    const commandResult = useSelectedMenuItem();
    if (typeof render === 'function') render();
    window.__hwanseDialogueCandidateMenuSelection = {
      ok: true,
      before,
      commandResult,
      afterMenuMode: menuMode,
      afterMenuOpen: menuOpen,
      selection: window.HWANSE_LAST_DIALOGUE_CANDIDATE_MENU_SELECTION || null,
      activeDialogue: activeDialogue ? {
        blockId: activeDialogue.block?.blockId || '',
        index: activeDialogue.index ?? null,
        lineCount: activeDialogue.lines?.length || 0,
        firstLine: activeDialogue.lines?.[0] || '',
        vmReplaySource: activeDialogue.vmReplay?.source || '',
        vmPartial: activeDialogue.vmReplay?.browserEventVmPartialReplayImplemented ?? null,
        vmFull: activeDialogue.vmReplay?.browserEventVmFullImplementation ?? null,
      } : null,
      dialogueOpenSoundLog: window.HWANSE_DIALOGUE_OPEN_SOUND_LOG || [],
      dialogueOpenSoundLast: window.HWANSE_LAST_DIALOGUE_OPEN_SOUND || null,
      dialogueChrome: window.HWANSE_LAST_DIALOGUE_BOX_RENDER || null,
    };
  })
  .catch((error) => {
    window.__hwanseDialogueCandidateMenuSelection = { ok: false, error: String(error && error.message || error) };
  });
return true;
"""


def dialogue_candidate_menu_state_script() -> str:
    return "return window.__hwanseDialogueCandidateMenuSelection || null;"


def restore_dialogue_completion_script() -> str:
    return """
window.__hwanseCandidateDialogueCompletionRestore = null;
quickLoadRuntime()
  .then((loaded) => ensureEventDialogueBlocks().then(() => {
    updateDialogueButton();
    const completion = publishPrototypeCompletionState();
    const button = document.getElementById('dialogueButton');
    render();
    window.__hwanseCandidateDialogueCompletionRestore = {
      loaded,
      scene,
      map: map?.name || '',
      nextCandidateId: activeActionDialogueCandidate()?.blockId || '',
      buttonText: button?.textContent || '',
      buttonTitle: button?.title || '',
      actionPrompt: window.HWANSE_LAST_ACTION_PROMPT || null,
      completion,
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      storyFlags: window.HWANSE_LAST_PROTOTYPE_STORY_FLAGS || null,
      labels: menuItems().map((item) => menuItemLabel(item)),
    };
  }))
  .catch((error) => {
    window.__hwanseCandidateDialogueCompletionRestore = { error: String(error && error.message || error) };
  });
return true;
"""


def complete_all_dialogue_script() -> str:
    return """
window.__hwanseCandidateDialogueAllCompletion = null;
function resetDialogueCompletionFeedback() {
  window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_LOG = [];
  window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_RENDER = [];
  window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK = null;
  window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK_RENDER = null;
  if (typeof activeDialogueCompletionFeedbacks !== 'undefined') activeDialogueCompletionFeedbacks = [];
}
ensureEventDialogueBlocks()
  .then(() => {
    updateDialogueButton();
    render();
    const beforePrompt = window.HWANSE_LAST_ACTION_PROMPT || null;
    const started = startDialogueCandidateAction();
    const openedId = activeDialogue?.block?.blockId || '';
    if (!started || openedId !== 'event-dialogue-block-026') {
      throw new Error(`remaining dialogue candidate did not start: ${openedId}`);
    }
    let guard = 0;
    while (activeDialogue && guard < 64) {
      advanceDialogue();
      guard += 1;
    }
    updateDialogueButton();
    render();
    const completedPrompt = window.HWANSE_LAST_ACTION_PROMPT || null;
    const completedActionResult = activateDialogueCandidateAtFoot();
    const completionNotice = window.HWANSE_LAST_DIALOGUE_COMPLETION_NOTICE || null;
    const completionNoticeBlock = activeDialogue ? {
      blockId: activeDialogue.block?.blockId || '',
      line: activeDialogue.lines?.[activeDialogue.index] || '',
      lineCount: activeDialogue.lines?.length || 0,
    } : null;
    const progressAfterCompletedAction = window.HWANSE_LAST_PROTOTYPE_PROGRESS || null;
    activeDialogue = null;
    const objectiveBefore = prototypeObjectiveState();
    window.HWANSE_LAST_OBJECTIVE_ACTION = null;
    window.HWANSE_LAST_DIALOGUE_COMPLETION_NOTICE = null;
    resetDialogueCompletionFeedback();
    const objectiveResult = activatePrototypeObjectiveAction();
    render();
    const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
    const objectiveCompletionNotice = window.HWANSE_LAST_DIALOGUE_COMPLETION_NOTICE || null;
    const objectiveDialogueCompletionFeedbackLog = window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_LOG || [];
    const objectiveDialogueCompletionFeedbackRender = window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_RENDER || [];
    const objectiveDialogueCompletionFeedbackLast = window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK || null;
    const objectiveDialogueCompletionFeedbackLastRender = window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK_RENDER || null;
    const objectiveActiveDialogueBlock = activeDialogue ? {
      blockId: activeDialogue.block?.blockId || '',
      line: activeDialogue.lines?.[activeDialogue.index] || '',
      lineCount: activeDialogue.lines?.length || 0,
    } : null;
    activeDialogue = null;
    const autoSave = window.HWANSE_LAST_DIALOGUE_COMPLETE_AUTO_SAVE || null;
    const raw = localStorage.getItem(RUNTIME_SAVE_KEY) || '{}';
    const payload = JSON.parse(raw);
    const menuEntries = menuItems();
    const labels = menuEntries.map((item) => menuItemLabel(item));
    const menuDirectDialogueIndex = menuEntries.findIndex((item) => item.command === 'startDialogueCandidate');
    const menuDialogueIndex = menuEntries.findIndex((item) => item.command === 'openDialogueCandidateMenu');
    const menuDialogueName = menuEntries[menuDialogueIndex]?.name || '';
    selectedMenuItemIndex = menuDialogueIndex;
    const menuDialogueResult = useSelectedMenuItem();
    window.setTimeout(() => {
      const menuCandidateEntries = menuItems();
      window.__hwanseCandidateDialogueAllCompletion = {
        saved: autoSave?.saved === true,
        autoSave,
        scene,
        map: map?.name || '',
        openedId,
        beforePrompt,
        completedPrompt,
        completedActionResult,
        completionNotice,
        completionNoticeBlock,
        progressAfterCompletedAction,
        objectiveBefore,
        objectiveResult,
        objectiveAction,
        objectiveCompletionNotice,
        objectiveDialogueCompletionFeedbackLog,
        objectiveDialogueCompletionFeedbackRender,
        objectiveDialogueCompletionFeedbackLast,
        objectiveDialogueCompletionFeedbackLastRender,
        objectiveActiveDialogueBlock,
        menuDirectDialogueIndex,
        menuDialogueName,
        menuDialogueResult,
        menuModeAfterOpen: menuMode,
        menuOpenAfterOpen: menuOpen,
        menuCandidateCount: menuCandidateEntries.filter((item) => item.command === 'selectDialogueCandidate').length,
        menuCandidateNames: menuCandidateEntries.map((item) => menuItemLabel(item)),
        nextCandidateId: activeActionDialogueCandidate()?.blockId || '',
        buttonText: document.getElementById('dialogueButton')?.textContent || '',
        completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
        progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
        storyFlags: window.HWANSE_LAST_PROTOTYPE_STORY_FLAGS || null,
        savedProgress: payload.prototypeProgress || null,
        savedStoryFlags: payload.prototypeStoryFlags || null,
        labels,
      };
    }, 0);
  })
  .catch((error) => {
    window.__hwanseCandidateDialogueAllCompletion = { error: String(error && error.message || error) };
  });
return true;
"""


def dialogue_completion_state_script() -> str:
    return "return window.__hwanseCandidateDialogueCompletion || null;"


def dialogue_completion_restore_state_script() -> str:
    return "return window.__hwanseCandidateDialogueCompletionRestore || null;"


def dialogue_all_completion_state_script() -> str:
    return "return window.__hwanseCandidateDialogueAllCompletion || null;"


def title_state_script() -> str:
    return """
const titleMenu = typeof titleMenuItems === 'function' ? titleMenuItems() : [];
return {
  readyState: document.readyState,
  search: location.search,
  hasScreen: !!document.getElementById('screen'),
  scene: typeof scene === 'undefined' ? '' : scene,
  titleLoaded: !!images?.title?.complete && (images.title.naturalWidth || 0) > 0,
  quickLoadHidden: document.getElementById('quickLoadButton')?.hidden ?? null,
  quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
  titleMenuKeys: titleMenu.map((item) => item.key),
  titleMenuLabels: titleMenu.map((item) => item.label),
};
"""


def click_title_continue_script() -> str:
    return """
if (typeof updateRuntimeSaveControls === 'function') updateRuntimeSaveControls();
const items = typeof titleMenuItems === 'function' ? titleMenuItems() : [];
const button = document.getElementById('quickLoadButton');
if (!button || button.hidden || button.textContent !== '이어하기' || !items.some((item) => item.key === 'continue')) {
  return {
    ok: false,
    hidden: button?.hidden ?? null,
    text: button?.textContent || '',
    titleMenuKeys: items.map((item) => item.key),
  };
}
const index = items.findIndex((item) => item.key === 'continue');
selectedTitleMenuIndex = index;
activateSelectedTitleMenuItem();
return {
  ok: index >= 0,
  text: button.textContent,
  title: button.title,
  titleMenuKeys: items.map((item) => item.key),
  selectedTitleMenuIndex,
};
"""


def continued_dialogue_map_state_script() -> str:
    return """
const foot = typeof footTile === 'function' ? footTile() : null;
return {
  readyState: document.readyState,
  search: location.search,
  hasScreen: !!document.getElementById('screen'),
  scene: typeof scene === 'undefined' ? '' : scene,
  map: typeof map === 'undefined' || !map ? '' : map.name,
  foot,
  quickLoadHidden: document.getElementById('quickLoadButton')?.hidden ?? null,
  quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
  playHudLines: window.HWANSE_LAST_PLAY_HUD_LINES || [],
  progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
  storyFlags: window.HWANSE_LAST_PROTOTYPE_STORY_FLAGS || null,
};
"""


def start_title_continue_restore_script() -> str:
    return """
window.__hwanseCandidateDialogueTitleContinueRestore = null;
function resetDialogueCompletionFeedback() {
  window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_LOG = [];
  window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_RENDER = [];
  window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK = null;
  window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK_RENDER = null;
  if (typeof activeDialogueCompletionFeedbacks !== 'undefined') activeDialogueCompletionFeedbacks = [];
}
Promise.all([ensureEventDialogueBlocks()]).then(() => {
  updateDialogueButton();
  const completion = publishPrototypeCompletionState();
  const button = document.getElementById('dialogueButton');
  render();
  const labels = menuItems().map((item) => menuItemLabel(item));
  const actionPrompt = window.HWANSE_LAST_ACTION_PROMPT || null;
  const titleContinueMap = {
    search: location.search,
    quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
    playHudLines: window.HWANSE_LAST_PLAY_HUD_LINES || [],
    foot: typeof footTile === 'function' ? footTile() : null,
  };
  const objectiveBefore = prototypeObjectiveState();
  window.HWANSE_LAST_DIALOGUE_COMPLETION_NOTICE = null;
  window.HWANSE_LAST_OBJECTIVE_ACTION = null;
  resetDialogueCompletionFeedback();
  const objectiveResult = activatePrototypeObjectiveAction();
  render();
  const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
  const objectiveCompletionNotice = window.HWANSE_LAST_DIALOGUE_COMPLETION_NOTICE || null;
  const objectiveDialogueCompletionFeedbackLog = window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_LOG || [];
  const objectiveDialogueCompletionFeedbackRender = window.HWANSE_DIALOGUE_COMPLETION_FEEDBACK_RENDER || [];
  const objectiveDialogueCompletionFeedbackLast = window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK || null;
  const objectiveDialogueCompletionFeedbackLastRender = window.HWANSE_LAST_DIALOGUE_COMPLETION_FEEDBACK_RENDER || null;
  const objectiveActiveDialogueBlock = activeDialogue ? {
    blockId: activeDialogue.block?.blockId || '',
    line: activeDialogue.lines?.[activeDialogue.index] || '',
    lineCount: activeDialogue.lines?.length || 0,
  } : null;
  activeDialogue = null;
  window.HWANSE_LAST_DIALOGUE_COMPLETION_NOTICE = null;
  return Promise.resolve(activateDialogueButton()).then((dialogueButtonResult) => {
    const completionNotice = window.HWANSE_LAST_DIALOGUE_COMPLETION_NOTICE || null;
    const completionNoticeBlock = activeDialogue ? {
      blockId: activeDialogue.block?.blockId || '',
      line: activeDialogue.lines?.[activeDialogue.index] || '',
      lineCount: activeDialogue.lines?.length || 0,
    } : null;
    activeDialogue = null;
    const menuEntries = menuItems();
    const menuDirectDialogueIndex = menuEntries.findIndex((item) => item.command === 'startDialogueCandidate');
    const menuDialogueIndex = menuEntries.findIndex((item) => item.command === 'openDialogueCandidateMenu');
    const menuDialogueName = menuEntries[menuDialogueIndex]?.name || '';
    selectedMenuItemIndex = menuDialogueIndex;
    window.HWANSE_LAST_DIALOGUE_COMPLETION_NOTICE = null;
    const menuDialogueResult = useSelectedMenuItem();
    window.setTimeout(() => {
      const menuCandidateEntries = menuItems();
      window.__hwanseCandidateDialogueTitleContinueRestore = {
        titleContinue: true,
        scene,
        map: map?.name || '',
        foot: typeof footTile === 'function' ? footTile() : null,
        titleContinueMap,
        dialogueButtonText: button?.textContent || '',
        dialogueButtonTitle: button?.title || '',
        labels,
        labelsAfterDialogueButton: menuItems().map((item) => menuItemLabel(item)),
        actionPrompt,
        objectiveBefore,
        objectiveResult,
        objectiveAction,
        objectiveCompletionNotice,
        objectiveDialogueCompletionFeedbackLog,
        objectiveDialogueCompletionFeedbackRender,
        objectiveDialogueCompletionFeedbackLast,
        objectiveDialogueCompletionFeedbackLastRender,
        objectiveActiveDialogueBlock,
        dialogueButtonResult,
        completionNotice,
        completionNoticeBlock,
        menuDirectDialogueIndex,
        menuDialogueName,
        menuDialogueResult,
        menuModeAfterOpen: menuMode,
        menuOpenAfterOpen: menuOpen,
        menuCandidateCount: menuCandidateEntries.filter((item) => item.command === 'selectDialogueCandidate').length,
        menuCandidateNames: menuCandidateEntries.map((item) => menuItemLabel(item)),
        completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || completion,
        progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
        storyFlags: window.HWANSE_LAST_PROTOTYPE_STORY_FLAGS || null,
        showDialogueCandidates,
      };
    }, 0);
  });
}).catch((error) => {
  window.__hwanseCandidateDialogueTitleContinueRestore = { error: String(error && error.message || error) };
});
return true;
"""


def title_continue_restore_state_script() -> str:
    return "return window.__hwanseCandidateDialogueTitleContinueRestore || null;"


def start_dialogue_display_style_probe_script() -> str:
    return """
window.__hwanseDialogueDisplayStyleProbe = null;
ensureEventDialogueBlocks()
  .then(() => {
    const block = eventDialogueBlockById('event-dialogue-block-029');
    if (!block) throw new Error('event-dialogue-block-029 not found');
    const started = startDialogue(block);
    if (!started) throw new Error('event-dialogue-block-029 did not start');
    render();
    const chrome = window.HWANSE_LAST_DIALOGUE_BOX_RENDER || null;
    const vm = chrome?.vmReplay || {};
    const renderEvent = chrome?.vmRenderEvent || null;
    const displayStyle = renderEvent?.displayStyleRender || vm.groundedControlDisplayStyleRender || null;
    const branchExecution = activeDialogue?.vmReplay?.groundedControlBranchExecution
      || vm.groundedControlBranchExecution
      || {};
    const firstBranchFontRowEvent = (branchExecution.fontRowEvents || [])[0] || null;
    const firstBranchDisplayStyleEvent = (branchExecution.displayStyleEvents || [])[0] || null;
    const firstBranchObjectPositionEvent = (branchExecution.objectPositionEvents || [])[0] || null;
    const firstBranchDisplayTimerEvent = (branchExecution.displayTimerEvents || [])[0] || null;
    window.__hwanseDialogueDisplayStyleProbe = {
      active: !!activeDialogue,
      blockId: activeDialogue?.block?.blockId || '',
      header: chrome?.header || '',
      lineCount: activeDialogue?.lines?.length || 0,
      textLayout: vm.groundedControlTextLayout || null,
      displayStyle,
      renderEvent,
      renderStateSnapshotCount: vm.renderStateSnapshotCount,
      browserEventVmRenderStateSnapshotImplemented: vm.browserEventVmRenderStateSnapshotImplemented,
      eventCount: vm.groundedControlExecutedEventCount,
      opcodeSet: vm.groundedControlExecutedOpcodeSet || [],
      branchFontRowEventCount: branchExecution.fontRowEventCount,
      branchFontRowLineCount: branchExecution.fontRowLineCount,
      branchFontRowFirst: firstBranchFontRowEvent,
      browserEventVmBranchFontRowImplemented:
        branchExecution.browserEventVmBranchFontRowImplemented === true,
      branchDisplayStyleEventCount: branchExecution.displayStyleEventCount,
      branchDisplayStyleLineCount: branchExecution.displayStyleLineCount,
      branchDisplayStyleFirst: firstBranchDisplayStyleEvent,
      browserEventVmBranchDisplayStyleImplemented:
        branchExecution.browserEventVmBranchDisplayStyleImplemented === true,
      branchObjectPositionEventCount: branchExecution.objectPositionEventCount,
      branchObjectPositionLineCount: branchExecution.objectPositionLineCount,
      branchObjectPositionFirst: firstBranchObjectPositionEvent,
      browserEventVmBranchObjectPositionImplemented:
        branchExecution.browserEventVmBranchObjectPositionImplemented === true,
      branchDisplayTimerEventCount: branchExecution.displayTimerEventCount,
      branchDisplayTimerLineCount: branchExecution.displayTimerLineCount,
      branchDisplayTimerFirst: firstBranchDisplayTimerEvent,
      browserEventVmBranchDisplayTimerImplemented:
        branchExecution.browserEventVmBranchDisplayTimerImplemented === true,
      browserEventVmFullImplementation: vm.browserEventVmFullImplementation,
      originalEventVmRuntimeImplemented: false,
    };
  })
  .catch((error) => {
    window.__hwanseDialogueDisplayStyleProbe = { error: String(error && error.message || error) };
  });
return true;
"""


def dialogue_display_style_probe_state_script() -> str:
    return "return window.__hwanseDialogueDisplayStyleProbe || null;"


def start_dialogue_cursor_relative_probe_script() -> str:
    return """
window.__hwanseDialogueCursorRelativeProbe = null;
ensureEventDialogueBlocks()
  .then(() => {
    const block = eventDialogueBlockById('event-dialogue-block-028');
    if (!block) throw new Error('event-dialogue-block-028 not found');
    const started = startDialogue(block);
    if (!started) throw new Error('event-dialogue-block-028 did not start');
    const firstCursorRender = (activeDialogue?.vmReplay?.renderEvents || []).find((event) =>
      event?.cursorLayout?.browserEventVmCursorRelativeLayoutImplemented === true
    );
    if (firstCursorRender && Number.isInteger(firstCursorRender.lineIndex)) {
      activeDialogue.index = firstCursorRender.lineIndex;
    }
    render();
    const chrome = window.HWANSE_LAST_DIALOGUE_BOX_RENDER || null;
    const vm = chrome?.vmReplay || {};
    const renderEvent = chrome?.vmRenderEvent || null;
    const cursorLayout = renderEvent?.cursorLayout || null;
    const branchExecution = activeDialogue?.vmReplay?.groundedControlBranchExecution
      || vm.groundedControlBranchExecution
      || {};
    const firstBranchCursorRelativeEvent = (branchExecution.cursorRelativeEvents || [])[0] || null;
    const currentBranchCursorRelativeEvent = (branchExecution.cursorRelativeEvents || []).find((event) =>
      event?.vaHex === cursorLayout?.vaHex
    ) || (branchExecution.cursorRelativeEvents || []).find((event) =>
      Number(event?.lineIndex) === Number(activeDialogue?.index ?? -1) &&
      Number(event?.rawX) === Number(cursorLayout?.rawX ?? -1) &&
      Number(event?.rawY) === Number(cursorLayout?.rawY ?? -1)
    ) || null;
    const firstBranchFontRowEvent = (branchExecution.fontRowEvents || [])[0] || null;
    const firstBranchDisplayStyleEvent = (branchExecution.displayStyleEvents || [])[0] || null;
    const firstBranchObjectPositionEvent = (branchExecution.objectPositionEvents || [])[0] || null;
    const firstBranchDisplayTimerEvent = (branchExecution.displayTimerEvents || [])[0] || null;
    const firstBranchDataBankEvent = (branchExecution.dataBankEvents || [])[0] || null;
    window.__hwanseDialogueCursorRelativeProbe = {
      active: !!activeDialogue,
      blockId: activeDialogue?.block?.blockId || '',
      header: chrome?.header || '',
      lineCount: activeDialogue?.lines?.length || 0,
      index: activeDialogue?.index ?? null,
      line: activeDialogue?.lines?.[activeDialogue?.index || 0] || '',
      groundedControlCursorRelativeCount: vm.groundedControlCursorRelativeCount,
      cursorRelativeLineLayoutCount: vm.cursorRelativeLineLayoutCount,
      browserEventVmCursorRelativeLayoutImplemented: vm.browserEventVmCursorRelativeLayoutImplemented,
      branchCursorRelativeEventCount: branchExecution.cursorRelativeEventCount,
      branchCursorRelativeLineCount: branchExecution.cursorRelativeLineCount,
      branchCursorRelativeFirst: firstBranchCursorRelativeEvent,
      branchCursorRelativeCurrent: currentBranchCursorRelativeEvent,
      browserEventVmBranchCursorRelativeImplemented:
        branchExecution.browserEventVmBranchCursorRelativeImplemented === true,
      branchFontRowEventCount: branchExecution.fontRowEventCount,
      branchFontRowFirst: firstBranchFontRowEvent,
      browserEventVmBranchFontRowImplemented:
        branchExecution.browserEventVmBranchFontRowImplemented === true,
      branchDisplayStyleEventCount: branchExecution.displayStyleEventCount,
      branchDisplayStyleFirst: firstBranchDisplayStyleEvent,
      browserEventVmBranchDisplayStyleImplemented:
        branchExecution.browserEventVmBranchDisplayStyleImplemented === true,
      branchObjectPositionEventCount: branchExecution.objectPositionEventCount,
      branchObjectPositionFirst: firstBranchObjectPositionEvent,
      browserEventVmBranchObjectPositionImplemented:
        branchExecution.browserEventVmBranchObjectPositionImplemented === true,
      branchDisplayTimerEventCount: branchExecution.displayTimerEventCount,
      branchDisplayTimerFirst: firstBranchDisplayTimerEvent,
      browserEventVmBranchDisplayTimerImplemented:
        branchExecution.browserEventVmBranchDisplayTimerImplemented === true,
      branchDataBankEventCount: branchExecution.dataBankEventCount,
      branchDataBankFirst: firstBranchDataBankEvent,
      browserEventVmBranchDataBankImplemented:
        branchExecution.browserEventVmBranchDataBankImplemented === true,
      renderEvent,
      cursorLayout,
      browserEventVmFullImplementation: vm.browserEventVmFullImplementation,
      originalEventVmRuntimeImplemented: false,
    };
  })
  .catch((error) => {
    window.__hwanseDialogueCursorRelativeProbe = { error: String(error && error.message || error) };
  });
return true;
"""


def dialogue_cursor_relative_probe_state_script() -> str:
    return "return window.__hwanseDialogueCursorRelativeProbe || null;"


def start_dialogue_literal_payload_probe_script() -> str:
    return """
window.__hwanseDialogueLiteralPayloadProbe = null;
ensureEventDialogueBlocks()
  .then(() => {
    const block = eventDialogueBlockById('event-dialogue-block-001');
    if (!block) throw new Error('event-dialogue-block-001 not found');
    const started = startDialogue(block);
    if (!started) throw new Error('event-dialogue-block-001 did not start');
    render();
    const firstChrome = window.HWANSE_LAST_DIALOGUE_BOX_RENDER || null;
    const firstVm = firstChrome?.vmReplay || {};
    const firstRenderEvent = firstChrome?.vmRenderEvent || null;
    const branchExecution = activeDialogue?.vmReplay?.groundedControlBranchExecution
      || firstVm.groundedControlBranchExecution
      || {};
    const literalLineAdvance = (branchExecution.lineAdvanceEvents || []).find((event) =>
      event?.browserEventVmLiteralTextPayloadLineImplemented === true
    ) || null;
    let lineAdvanceChrome = null;
    let lineAdvanceRenderEvent = null;
    if (literalLineAdvance && Number.isFinite(Number(literalLineAdvance.lineIndex))) {
      activeDialogue.index = Number(literalLineAdvance.lineIndex);
      render();
      lineAdvanceChrome = window.HWANSE_LAST_DIALOGUE_BOX_RENDER || null;
      lineAdvanceRenderEvent = lineAdvanceChrome?.vmRenderEvent || null;
    }
    window.__hwanseDialogueLiteralPayloadProbe = {
      active: !!activeDialogue,
      blockId: activeDialogue?.block?.blockId || '',
      header: firstChrome?.header || '',
      lineCount: activeDialogue?.lines?.length || 0,
      literalTextEventCount: firstVm.literalTextEventCount,
      literalTextSamples: firstVm.literalTextSamples || [],
      browserEventVmLiteralTextPayloadLineImplemented:
        firstVm.browserEventVmLiteralTextPayloadLineImplemented,
      branchLiteralTextPayloadEventCount: branchExecution.literalTextPayloadEventCount,
      branchLiteralTextPayloadExecutionCount: branchExecution.literalTextPayloadExecutionCount,
      branchLiteralTextMatchedLineCount: branchExecution.literalTextMatchedLineCount,
      branchExecutedOpcodeSet: branchExecution.executedOpcodeSet || [],
      firstRenderEvent,
      literalLineAdvance,
      lineAdvanceRenderEvent,
      browserEventVmFullImplementation: firstVm.browserEventVmFullImplementation,
      originalEventVmRuntimeImplemented: false,
    };
  })
  .catch((error) => {
    window.__hwanseDialogueLiteralPayloadProbe = { error: String(error && error.message || error) };
  });
return true;
"""


def dialogue_literal_payload_probe_state_script() -> str:
    return "return window.__hwanseDialogueLiteralPayloadProbe || null;"


def wait_for_completion_state(port: int, session_id: str, timeout: float = 8) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, dialogue_completion_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        dialogue = ((state.get("completion") or {}).get("dialogue") or {})
        if state and state.get("error") is None and dialogue.get("completedCount") == 1:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate dialogue completion did not finish: {state!r}")


def wait_for_dialogue_candidate_menu_state(port: int, session_id: str, timeout: float = 8) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, dialogue_candidate_menu_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        active_dialogue = state.get("activeDialogue") or {}
        if (
            state
            and state.get("error") is None
            and state.get("ok") is True
            and active_dialogue.get("blockId") == "event-dialogue-block-026"
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"dialogue candidate menu selection did not become ready: {state!r}")


def wait_for_restore_state(port: int, session_id: str, timeout: float = 8) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, dialogue_completion_restore_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        dialogue = ((state.get("completion") or {}).get("dialogue") or {})
        if state and state.get("error") is None and state.get("loaded") is True and dialogue.get("completedCount") == 1:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate dialogue completion restore did not finish: {state!r}")


def wait_for_all_completion_state(port: int, session_id: str, timeout: float = 8) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, dialogue_all_completion_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        dialogue = ((state.get("completion") or {}).get("dialogue") or {})
        notice = state.get("completionNotice") or {}
        if (
            state
            and state.get("error") is None
            and dialogue.get("completed") is True
            and notice.get("blockId") == "dialogue-complete:map4_08n"
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate dialogue all-completion did not finish: {state!r}")


def wait_for_continue_ready_title(port: int, session_id: str, timeout: float = 8) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, title_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        if (
            state.get("readyState") == "complete"
            and state.get("hasScreen") is True
            and state.get("scene") == "title"
            and state.get("titleLoaded") is True
            and state.get("quickLoadHidden") is False
            and state.get("quickLoadText") == "이어하기"
            and "continue" in (state.get("titleMenuKeys") or [])
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"title continue button did not become ready: {state!r}")


def wait_for_continued_dialogue_map(port: int, session_id: str, timeout: float = 8) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, continued_dialogue_map_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        foot = state.get("foot") or {}
        counts = ((state.get("progress") or {}).get("counts") or {})
        if (
            state.get("readyState") == "complete"
            and state.get("hasScreen") is True
            and state.get("scene") == "map"
            and state.get("map") == "map4_08n"
            and foot.get("x") == 11
            and foot.get("y") == 11
            and state.get("quickLoadHidden") is False
            and state.get("quickLoadText") == "임시 불러오기"
            and state.get("search") == "?map=map4_08n&startTile=11%2C11"
            and counts.get("dialogue-complete") == 2
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"title continue did not restore dialogue map: {state!r}")


def wait_for_title_continue_restore_state(port: int, session_id: str, timeout: float = 8) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, title_continue_restore_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        dialogue = ((state.get("completion") or {}).get("dialogue") or {})
        notice = state.get("completionNotice") or {}
        if (
            state
            and state.get("error") is None
            and state.get("titleContinue") is True
            and state.get("map") == "map4_08n"
            and dialogue.get("completed") is True
            and notice.get("blockId") == "dialogue-complete:map4_08n"
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate dialogue title continue restore did not finish: {state!r}")


def wait_for_display_style_probe_state(port: int, session_id: str, timeout: float = 8) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, dialogue_display_style_probe_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        style = state.get("displayStyle") or {}
        if (
            state
            and state.get("error") is None
            and state.get("blockId") == "event-dialogue-block-029"
            and style.get("styleName") == "emphasis"
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate dialogue display style probe did not finish: {state!r}")


def wait_for_cursor_relative_probe_state(port: int, session_id: str, timeout: float = 8) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, dialogue_cursor_relative_probe_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        if (
            state
            and state.get("error") is None
            and state.get("blockId") == "event-dialogue-block-028"
            and state.get("browserEventVmCursorRelativeLayoutImplemented") is True
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate dialogue cursor-relative probe did not finish: {state!r}")


def wait_for_literal_payload_probe_state(port: int, session_id: str, timeout: float = 8) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, dialogue_literal_payload_probe_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        if (
            state
            and state.get("error") is None
            and state.get("blockId") == "event-dialogue-block-001"
            and state.get("browserEventVmLiteralTextPayloadLineImplemented") is True
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate dialogue literal payload probe did not finish: {state!r}")


def verify_dialogue_candidate_menu_state(state: dict) -> None:
    marker = state.get("before") or {}
    menu_marker = marker.get("menuMarker") or {}
    selection = state.get("selection") or {}
    active_dialogue = state.get("activeDialogue") or {}
    open_sound = state.get("dialogueOpenSoundLast") or {}
    labels = [str(label) for label in marker.get("labels") or []]
    if (
        state.get("ok") is not True
        or marker.get("menuMode") != "dialogue-candidate"
        or marker.get("count", 0) < 3
        or marker.get("targetBlockId") != "event-dialogue-block-026"
        or marker.get("targetPreview") != "스마슈"
        or not any("event-dialogue-block-026" in str(row) for row in (menu_marker.get("choices") or []))
        or not any("스마슈" in label for label in labels)
        or menu_marker.get("source") != "prototype-dialogue-candidate-menu"
        or menu_marker.get("map") != "map4_08n"
        or menu_marker.get("count") != 2
        or menu_marker.get("originalEventVmRuntimeImplemented") is not False
        or menu_marker.get("originalStoryFlagRuntimeImplemented") is not False
        or state.get("commandResult") is not True
        or state.get("afterMenuMode") != "main"
        or state.get("afterMenuOpen") is not False
        or selection.get("source") != "prototype-dialogue-candidate-menu"
        or selection.get("map") != "map4_08n"
        or selection.get("blockId") != "event-dialogue-block-026"
        or selection.get("index") != 1
        or selection.get("count") != 2
        or selection.get("preview") != "스마슈"
        or selection.get("originalEventVmRuntimeImplemented") is not False
        or selection.get("originalStoryFlagRuntimeImplemented") is not False
        or active_dialogue.get("blockId") != "event-dialogue-block-026"
        or active_dialogue.get("index") != 0
        or active_dialogue.get("lineCount") != 14
        or active_dialogue.get("firstLine") != "스마슈"
        or active_dialogue.get("vmReplaySource") != "partial-opcode-replay"
        or active_dialogue.get("vmPartial") is not True
        or active_dialogue.get("vmFull") is not False
        or open_sound.get("source") != "dialogue-open-sound"
        or open_sound.get("blockId") != "event-dialogue-block-026"
        or open_sound.get("soundKey") != "dialogue"
        or open_sound.get("soundSrc") != "../extract_wlk/01.wav"
        or open_sound.get("browserDialogueOpenSoundImplemented") is not True
    ):
        raise WebDriverError(f"dialogue candidate menu selection did not open selected candidate: {state!r}")


def story_flag_key(block_id: str) -> str:
    return f"story:dialogue-complete:map4_08n:{block_id}"


def verify_story_flags(
    state: dict,
    *,
    expected_count: int,
    expected_blocks: list[str],
    require_saved: bool = False,
) -> None:
    story_flags = state.get("storyFlags") or {}
    progress_story_flags = ((state.get("progress") or {}).get("storyFlags") or {})
    expected_keys = {story_flag_key(block_id) for block_id in expected_blocks}
    keys = set((story_flags.get("flags") or {}).keys())
    records = story_flags.get("records") or []
    if (
        story_flags.get("source") != "prototype-progress-derived-story-flags"
        or story_flags.get("flagCount") != expected_count
        or story_flags.get("browserPrototypeStoryFlagImplemented") is not True
        or story_flags.get("originalStoryFlagRuntimeImplemented") is not False
        or progress_story_flags.get("flagCount") != expected_count
        or progress_story_flags.get("browserPrototypeStoryFlagImplemented") is not True
        or progress_story_flags.get("originalStoryFlagRuntimeImplemented") is not False
        or not expected_keys.issubset(keys)
        or not all(record.get("kind") == "dialogue-complete" for record in records)
    ):
        raise WebDriverError(f"candidate dialogue story flags are incomplete: {state!r}")
    if require_saved:
        saved_story_flags = state.get("savedStoryFlags") or {}
        saved_keys = set((saved_story_flags.get("flags") or {}).keys())
        if (
            saved_story_flags.get("source") != "prototype-progress-derived-story-flags"
            or saved_story_flags.get("flagCount") != expected_count
            or saved_story_flags.get("browserPrototypeStoryFlagImplemented") is not True
            or saved_story_flags.get("originalStoryFlagRuntimeImplemented") is not False
            or not expected_keys.issubset(saved_keys)
        ):
            raise WebDriverError(f"candidate dialogue saved story flags are incomplete: {state!r}")


def verify_completion_state(state: dict, restored: bool = False) -> None:
    completion = state.get("completion") or {}
    dialogue = completion.get("dialogue") or {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    labels = state.get("labels") or []
    if (
        state.get("scene") != "map"
        or state.get("map") != "map4_08n"
        or state.get("buttonText") != "대사 완료 1/2"
        or "대사 선택 2" not in labels
        or dialogue.get("count") != 2
        or dialogue.get("completed") is not False
        or dialogue.get("completedCount") != 1
        or dialogue.get("remainingCount") != 1
        or "event-dialogue-block-005" not in (dialogue.get("completedIds") or [])
        or state.get("nextCandidateId") != "event-dialogue-block-026"
        or dialogue.get("originalEventVmRuntimeImplemented") is not False
        or dialogue.get("originalStoryFlagRuntimeImplemented") is not False
        or completion.get("originalStoryFlagRuntimeImplemented") is not False
        or counts.get("dialogue-complete") != 1
        or progress.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate dialogue completion state is incomplete: {state!r}")
    verify_story_flags(
        state,
        expected_count=1,
        expected_blocks=["event-dialogue-block-005"],
        require_saved=not restored,
    )
    if restored:
        if state.get("loaded") is not True:
            raise WebDriverError(f"candidate dialogue completion restore is incomplete: {state!r}")
    else:
        auto_save = state.get("autoSave") or {}
        auto_save_counts = ((auto_save.get("progress") or {}).get("counts") or {})
        auto_save_tile = auto_save.get("payloadTile") or {}
        saved_progress = state.get("savedProgress") or {}
        saved_counts = saved_progress.get("counts") or {}
        mobile_cancel = state.get("mobileCancel") or {}
        mobile_before = mobile_cancel.get("before") or {}
        mobile_after = mobile_cancel.get("after") or {}
        opened_sound = mobile_cancel.get("openedSoundState") or {}
        reopened_sound = mobile_cancel.get("reopenedSoundState") or {}
        opened_sound_counts = opened_sound.get("counts") or {}
        reopened_sound_counts = reopened_sound.get("counts") or {}
        opened_sound_last = opened_sound.get("openLast") or {}
        reopened_sound_last = reopened_sound.get("openLast") or {}
        reopened_sound_log = reopened_sound.get("openLog") or []
        dialogue_chrome = mobile_before.get("dialogueChrome") or {}
        dialogue_chrome_vm = dialogue_chrome.get("vmReplay") or {}
        dialogue_chrome_layout = dialogue_chrome_vm.get("groundedControlTextLayout") or {}
        dialogue_display_style = dialogue_chrome_vm.get("groundedControlDisplayStyleRender") or {}
        dialogue_render_event = dialogue_chrome.get("vmRenderEvent") or {}
        dialogue_render_text_layout = dialogue_render_event.get("controlTextLayout") or {}
        dialogue_render_style = dialogue_render_event.get("displayStyleRender") or {}
        dialogue_branch_flow = dialogue_chrome_vm.get("groundedControlBranchFlow") or {}
        dialogue_branch_execution = dialogue_chrome_vm.get("groundedControlBranchExecution") or {}
        dialogue_branch_first_call = dialogue_branch_flow.get("firstCall") or {}
        dialogue_branch_first_return = dialogue_branch_flow.get("firstReturn") or {}
        dialogue_branch_execution_first_call = dialogue_branch_execution.get("firstCall") or {}
        dialogue_branch_execution_first_return = dialogue_branch_execution.get("firstReturn") or {}
        dialogue_branch_text_origin = dialogue_branch_execution.get("firstTextOriginEvent") or {}
        dialogue_branch_global_timer = dialogue_branch_execution.get("firstGlobalTimerEvent") or {}
        dialogue_object_focus = dialogue_chrome_vm.get("groundedControlObjectFocus") or {}
        dialogue_object_focus_render = dialogue_chrome_vm.get("groundedControlObjectFocusRendered") or {}
        pointer_advance = state.get("pointerAdvance") or {}
        pointer_before = pointer_advance.get("before") or {}
        pointer_after = pointer_advance.get("after") or {}
        pointer_release_after = pointer_advance.get("afterRelease") or {}
        pointer_after_chrome = pointer_after.get("dialogueChrome") or {}
        pointer_after_render_event = pointer_after_chrome.get("vmRenderEvent") or {}
        pointer_after_render_layout = pointer_after_render_event.get("controlTextLayout") or {}
        pointer_after_latch = pointer_after.get("latch") or {}
        pointer_release_latch = pointer_release_after.get("latch") or {}
        branch_line_advance_probe = state.get("branchLineAdvanceProbe") or {}
        branch_line_advance_event = branch_line_advance_probe.get("lineAdvanceEvent") or {}
        branch_line_advance_render_event = branch_line_advance_probe.get("renderEvent") or {}
        branch_wait_probe = state.get("branchWaitProbe") or {}
        branch_wait_event = branch_wait_probe.get("waitEvent") or {}
        branch_wait_latch = branch_wait_probe.get("latch") or {}
        branch_wait_chrome_vm = (branch_wait_probe.get("chrome") or {}).get("vmReplay") or {}
        branch_wait_chrome_event = branch_wait_chrome_vm.get("groundedControlBranchWaitEvent") or {}
        keyboard_latch = state.get("keyboardInputWaitLatch") or {}
        keyboard_before = keyboard_latch.get("before") or {}
        keyboard_first = keyboard_latch.get("afterFirst") or {}
        keyboard_repeat = keyboard_latch.get("afterRepeat") or {}
        keyboard_release = keyboard_latch.get("afterRelease") or {}
        keyboard_second = keyboard_latch.get("afterSecondAdvance") or {}
        keyboard_first_latch = keyboard_first.get("latch") or {}
        keyboard_repeat_latch = keyboard_repeat.get("latch") or {}
        keyboard_release_latch = keyboard_release.get("latch") or {}
        keyboard_second_latch = keyboard_second.get("latch") or {}
        pointer_sound = pointer_advance.get("soundState") or {}
        pointer_sound_counts = pointer_sound.get("counts") or {}
        pointer_advance_last = pointer_sound.get("advanceLast") or {}
        keyboard_sound_first = keyboard_latch.get("soundAfterFirst") or {}
        keyboard_sound_repeat = keyboard_latch.get("soundAfterRepeat") or {}
        keyboard_sound_second = keyboard_latch.get("soundAfterSecondAdvance") or {}
        keyboard_first_counts = keyboard_sound_first.get("counts") or {}
        keyboard_repeat_counts = keyboard_sound_repeat.get("counts") or {}
        keyboard_second_counts = keyboard_sound_second.get("counts") or {}
        keyboard_first_advance_last = keyboard_sound_first.get("advanceLast") or {}
        keyboard_repeat_advance_last = keyboard_sound_repeat.get("advanceLast") or {}
        keyboard_second_advance_last = keyboard_sound_second.get("advanceLast") or {}
        dialogue_sound = state.get("dialogueSoundState") or {}
        dialogue_sound_counts = dialogue_sound.get("counts") or {}
        dialogue_sound_last = dialogue_sound.get("last") or {}
        dialogue_advance_log = dialogue_sound.get("advanceLog") or []
        dialogue_advance_first = dialogue_advance_log[0] if dialogue_advance_log else {}
        dialogue_advance_last = dialogue_sound.get("advanceLast") or {}
        before_prompt = state.get("beforePrompt") or {}
        prompt_activation = state.get("promptActivation") or {}
        prompt_activation_prompt = prompt_activation.get("prompt") or {}
        action_prompt = state.get("actionPrompt") or {}
        feedback = state.get("dialogueFeedbackLast") or {}
        feedback_render = state.get("dialogueFeedbackLastRender") or {}
        completion_checks = [
            ("saved", state.get("saved") is True),
            ("auto_save.saved", auto_save.get("saved") is True),
            ("auto_save.source", auto_save.get("source") == "dialogue-complete"),
            ("auto_save.blockId", auto_save.get("blockId") == "event-dialogue-block-005"),
            ("auto_save.payloadMap", auto_save.get("payloadMap") == "map4_08n"),
            ("auto_save_tile.x", auto_save_tile.get("x") == 11),
            ("auto_save_tile.y", auto_save_tile.get("y") == 11),
            ("auto_save_counts.dialogue-complete", auto_save_counts.get("dialogue-complete") == 1),
            ("auto_save.originalEventVmRuntimeImplemented", auto_save.get("originalEventVmRuntimeImplemented") is False),
            ("auto_save.originalStoryFlagRuntimeImplemented", auto_save.get("originalStoryFlagRuntimeImplemented") is False),
            ("openedId", state.get("openedId") == "event-dialogue-block-005"),
            ("before_prompt.kind", before_prompt.get("kind") == "dialogue-candidate"),
            ("before_prompt.blockId", before_prompt.get("blockId") == "event-dialogue-block-005"),
            ("before_prompt.completed", before_prompt.get("completed") is False),
            ("before_prompt.text", before_prompt.get("text") == "Enter -> 대사 1/2"),
            ("prompt_activation.activated", prompt_activation.get("activated") is True),
            ("prompt_activation.blockId", prompt_activation.get("blockId") == "event-dialogue-block-005"),
            ("prompt_activation.lineCount", prompt_activation.get("lineCount") == 13),
            ("prompt_activation_prompt.kind", prompt_activation_prompt.get("kind") == "dialogue-candidate"),
            (
                "prompt_activation.originalEventVmRuntimeImplemented",
                prompt_activation.get("originalEventVmRuntimeImplemented") is False,
            ),
            ("mobile_cancel.restarted", mobile_cancel.get("restarted") is True),
            ("mobile_cancel.reopenedId", mobile_cancel.get("reopenedId") == "event-dialogue-block-005"),
            ("opened_sound_counts.dialogue", opened_sound_counts.get("dialogue") == 1),
            ("opened_sound.source", opened_sound_last.get("source") == "dialogue-open-sound"),
            ("opened_sound.blockId", opened_sound_last.get("blockId") == "event-dialogue-block-005"),
            ("opened_sound.lineIndex", opened_sound_last.get("lineIndex") == 0),
            ("opened_sound.lineCount", opened_sound_last.get("lineCount") == 13),
            ("opened_sound.soundKey", opened_sound_last.get("soundKey") == "dialogue"),
            ("opened_sound.soundSrc", opened_sound_last.get("soundSrc") == "../extract_wlk/01.wav"),
            (
                "opened_sound.browserDialogueOpenSoundImplemented",
                opened_sound_last.get("browserDialogueOpenSoundImplemented") is True,
            ),
            (
                "opened_sound.originalEventVmRuntimeImplemented",
                opened_sound_last.get("originalEventVmRuntimeImplemented") is False,
            ),
            (
                "opened_sound.originalStoryFlagRuntimeImplemented",
                opened_sound_last.get("originalStoryFlagRuntimeImplemented") is False,
            ),
            ("reopened_sound_counts.dialogue", reopened_sound_counts.get("dialogue") == 2),
            ("reopened_sound_log.count", len(reopened_sound_log) == 2),
            ("reopened_sound.source", reopened_sound_last.get("source") == "dialogue-open-sound"),
            ("reopened_sound.blockId", reopened_sound_last.get("blockId") == "event-dialogue-block-005"),
            ("reopened_sound.soundSrc", reopened_sound_last.get("soundSrc") == "../extract_wlk/01.wav"),
            ("mobile_before.active", mobile_before.get("active") is True),
            ("mobile_before.blockId", mobile_before.get("blockId") == "event-dialogue-block-005"),
            ("mobile_before.index", mobile_before.get("index") == 0),
            ("mobile_before.hidden", mobile_before.get("hidden") is False),
            ("mobile_before.text", mobile_before.get("text") == "B"),
            ("mobile_before.scene", mobile_before.get("scene") == "map"),
            ("dialogue_chrome.blockId", dialogue_chrome.get("blockId") == "event-dialogue-block-005"),
            ("dialogue_chrome.header", dialogue_chrome.get("header") == "대화 1/13"),
            ("dialogue_chrome.internalIdHidden", dialogue_chrome.get("internalIdHidden") is True),
            ("dialogue_vm.effectsExecuted", dialogue_chrome_vm.get("groundedControlEffectsExecuted") is True),
            ("dialogue_vm.executedEventCount", dialogue_chrome_vm.get("groundedControlExecutedEventCount") == 15),
            (
                "dialogue_vm.executedOpcodeSet",
                dialogue_chrome_vm.get("groundedControlExecutedOpcodeSet")
                == ["0x03", "0x06", "0x07", "0x08", "0x09", "0x0a", "0x15", "0x1b", "0x37"],
            ),
            ("dialogue_layout.originX", dialogue_chrome_layout.get("originX") == 16),
            ("dialogue_layout.originY", dialogue_chrome_layout.get("originY") == 16),
            ("dialogue_vm.waitForInputReleaseCount", dialogue_chrome_vm.get("groundedControlWaitForInputReleaseCount") == 4),
            ("dialogue_vm.renderStateSnapshotCount", dialogue_chrome_vm.get("renderStateSnapshotCount") == 2),
            (
                "dialogue_vm.browserEventVmRenderStateSnapshotImplemented",
                dialogue_chrome_vm.get("browserEventVmRenderStateSnapshotImplemented") is True,
            ),
            ("dialogue_vm.branchCallCount", dialogue_chrome_vm.get("groundedControlBranchCallCount") == 4),
            ("dialogue_vm.stackReturnCount", dialogue_chrome_vm.get("groundedControlStackReturnCount") == 1),
            ("dialogue_vm.dataBank", dialogue_chrome_vm.get("groundedControlDataBank") == 0),
            ("dialogue_vm.dataBankSelectCount", dialogue_chrome_vm.get("groundedControlDataBankSelectCount") == 1),
            ("branch_flow.source", dialogue_branch_flow.get("source") == "event-vm-control-flow-summary"),
            ("branch_flow.callCount", dialogue_branch_flow.get("callCount") == 4),
            ("branch_flow.returnCount", dialogue_branch_flow.get("returnCount") == 1),
            ("branch_flow.maxDepth", dialogue_branch_flow.get("maxDepth") == 4),
            ("branch_flow.finalDepth", dialogue_branch_flow.get("finalDepth") == 4),
            ("branch_flow.underflowReturnCount", dialogue_branch_flow.get("underflowReturnCount") == 1),
            ("branch_first_call.target", dialogue_branch_first_call.get("branchTargetVaHex") == "0x0045ee2c"),
            ("branch_first_call.continuation", dialogue_branch_first_call.get("continuationVaHex") == "0x0045ee64"),
            ("branch_first_return.underflow", dialogue_branch_first_return.get("underflow") is True),
            (
                "branch_flow.browserEventVmControlFlowSummaryImplemented",
                dialogue_branch_flow.get("browserEventVmControlFlowSummaryImplemented") is True,
            ),
            (
                "branch_flow.originalEventVmRuntimeImplemented",
                dialogue_branch_flow.get("originalEventVmRuntimeImplemented") is False,
            ),
            ("branch_execution.source", dialogue_branch_execution.get("source") == "event-vm-known-branch-execution"),
            ("branch_execution.stepCount", dialogue_branch_execution.get("stepCount") == 62),
            ("branch_execution.haltedReason", dialogue_branch_execution.get("haltedReason") == "end-of-known-instructions"),
            ("branch_execution.callCount", dialogue_branch_execution.get("callCount") == 4),
            ("branch_execution.returnCount", dialogue_branch_execution.get("returnCount") == 5),
            ("branch_execution.underflowReturnCount", dialogue_branch_execution.get("underflowReturnCount") == 1),
            ("branch_execution.maxDepth", dialogue_branch_execution.get("maxDepth") == 1),
            ("branch_execution.finalDepth", dialogue_branch_execution.get("finalDepth") == 0),
            ("branch_execution.renderEventCount", dialogue_branch_execution.get("renderEventCount") == 6),
            ("branch_execution.renderLineCount", dialogue_branch_execution.get("renderLineCount") == 6),
            ("branch_execution.lineAdvanceEventCount", dialogue_branch_execution.get("lineAdvanceEventCount") == 8),
            ("branch_execution.lineAdvanceLineCount", dialogue_branch_execution.get("lineAdvanceLineCount") == 8),
            ("branch_execution.waitEventCount", dialogue_branch_execution.get("waitEventCount") == 4),
            ("branch_execution.waitLineCount", dialogue_branch_execution.get("waitLineCount") == 4),
            ("branch_execution.waitForInputReleaseCount", dialogue_branch_execution.get("waitForInputReleaseCount") == 4),
            ("branch_execution.textOriginEventCount", dialogue_branch_execution.get("textOriginEventCount") == 5),
            ("branch_execution.textOriginVa", dialogue_branch_text_origin.get("vaHex") == "0x0045ee44"),
            ("branch_execution.textOriginX", dialogue_branch_text_origin.get("originX") == 16),
            ("branch_execution.textOriginY", dialogue_branch_text_origin.get("originY") == 16),
            (
                "branch_execution.browserEventVmBranchTextOriginImplemented",
                dialogue_branch_execution.get("browserEventVmBranchTextOriginImplemented") is True,
            ),
            ("branch_execution.globalTimerEventCount", dialogue_branch_execution.get("globalTimerEventCount") == 5),
            ("branch_execution.globalTimerVa", dialogue_branch_global_timer.get("vaHex") == "0x0045ee4c"),
            (
                "branch_execution.globalTimerSeed",
                (dialogue_branch_global_timer.get("globalTimerState") or {}).get("seed") == 0,
            ),
            (
                "branch_execution.browserEventVmBranchGlobalTimerImplemented",
                dialogue_branch_execution.get("browserEventVmBranchGlobalTimerImplemented") is True,
            ),
            ("branch_execution.missingTransferCount", dialogue_branch_execution.get("missingTransferCount") == 0),
            ("branch_execution.repeatedInstructionCount", dialogue_branch_execution.get("repeatedInstructionCount") == 9),
            ("branch_execution.maxVisitCount", dialogue_branch_execution.get("maxVisitCount") == 5),
            (
                "branch_execution.executedOpcodeSet",
                dialogue_branch_execution.get("executedOpcodeSet")
                == ["0x02", "0x03", "0x06", "0x07", "0x08", "0x09", "0x0a", "0x0b", "0x0d", "0x15", "0x1b", "0x37"],
            ),
            (
                "branch_execution.browserEventVmBranchExecutionImplemented",
                dialogue_branch_execution.get("browserEventVmBranchExecutionImplemented") is True,
            ),
            (
                "branch_execution.browserEventVmFullImplementation",
                dialogue_branch_execution.get("browserEventVmFullImplementation") is False,
            ),
            (
                "branch_execution.originalEventVmRuntimeImplemented",
                dialogue_branch_execution.get("originalEventVmRuntimeImplemented") is False,
            ),
            (
                "branch_execution.firstCallTarget",
                dialogue_branch_execution_first_call.get("branchTargetVaHex") == "0x0045ee2c",
            ),
            (
                "branch_execution.firstCallContinuation",
                dialogue_branch_execution_first_call.get("continuationVaHex") == "0x0045ee64",
            ),
            (
                "branch_execution.firstCallMatched",
                dialogue_branch_execution_first_call.get("targetMatched") is True,
            ),
            (
                "branch_execution.firstReturnUnderflow",
                dialogue_branch_execution_first_return.get("underflow") is True,
            ),
            ("branch_line_advance_probe.active", branch_line_advance_probe.get("active") is True),
            ("branch_line_advance_probe.blockId", branch_line_advance_probe.get("blockId") == "event-dialogue-block-005"),
            (
                "branch_line_advance.lineIndex",
                branch_line_advance_render_event.get("lineIndex") == branch_line_advance_probe.get("index"),
            ),
            (
                "branch_line_advance.line",
                branch_line_advance_render_event.get("line") == branch_line_advance_probe.get("line"),
            ),
            (
                "branch_line_advance.source",
                branch_line_advance_render_event.get("source") == "branch-line-advance",
            ),
            (
                "branch_line_advance.eventSource",
                branch_line_advance_event.get("lineSource") == "branch-separator-line-advance",
            ),
            (
                "branch_line_advance.selection",
                branch_line_advance_render_event.get("browserEventVmBranchLineAdvanceSelectionImplemented") is True,
            ),
            (
                "branch_line_advance.implemented",
                branch_line_advance_render_event.get("browserEventVmBranchLineAdvanceImplemented") is True,
            ),
            ("branch_wait_probe.active", branch_wait_probe.get("active") is True),
            ("branch_wait_probe.blockId", branch_wait_probe.get("blockId") == "event-dialogue-block-005"),
            ("branch_wait.eventCount", branch_wait_event.get("browserEventVmBranchInputWaitImplemented") is True),
            ("branch_wait.latchBranchLine", branch_wait_latch.get("browserDialogueInputWaitBranchLineImplemented") is True),
            (
                "branch_wait.latchLineIndex",
                (branch_wait_latch.get("branchWaitEvent") or {}).get("lineIndex") == branch_wait_probe.get("index"),
            ),
            (
                "branch_wait.chromeLineIndex",
                branch_wait_chrome_event.get("lineIndex") == branch_wait_probe.get("index"),
            ),
            (
                "branch_wait.chromeImplemented",
                branch_wait_chrome_event.get("browserEventVmBranchInputWaitImplemented") is True,
            ),
            ("dialogue_vm.objectPositionCount", dialogue_chrome_vm.get("groundedControlObjectPositionCount") == 1),
            ("display_style.source", dialogue_display_style.get("source") == "event-vm-display-style-render"),
            ("display_style.fontRow", dialogue_display_style.get("fontRow") == 0),
            ("display_style.styleMode", dialogue_display_style.get("styleMode") == 0),
            ("display_style.styleTableIndex", dialogue_display_style.get("styleTableIndex") == 0),
            ("display_style.styleName", dialogue_display_style.get("styleName") == "default"),
            ("display_style.textColor", dialogue_display_style.get("textColor") == "#f5eec8"),
            ("display_style.headerColor", dialogue_display_style.get("headerColor") == "#9bd4ff"),
            ("display_style.fontPx", dialogue_display_style.get("fontPx") == 17),
            ("display_style.lineHeight", dialogue_display_style.get("lineHeight") == 24),
            ("display_style.globalTimerSeed", (dialogue_display_style.get("globalTimerState") or {}).get("seed") == 0),
            ("display_style.timerSeed", dialogue_display_style.get("timerSeed") == 0),
            (
                "display_style.browserEventVmDisplayStyleImplemented",
                dialogue_display_style.get("browserEventVmDisplayStyleImplemented") is True,
            ),
            (
                "display_style.browserEventVmDisplayTimerStateImplemented",
                dialogue_display_style.get("browserEventVmDisplayTimerStateImplemented") is True,
            ),
            (
                "display_style.originalEventVmRuntimeImplemented",
                dialogue_display_style.get("originalEventVmRuntimeImplemented") is False,
            ),
            ("render_state.vaHex", dialogue_render_event.get("vaHex") == "0x0045ee54"),
            ("render_state.lineIndex", dialogue_render_event.get("lineIndex") == 0),
            ("render_state.textSource", dialogue_render_event.get("textSourceValueHex") == "0x00030000"),
            ("render_state.line", dialogue_render_event.get("line") == "주작권사"),
            ("render_state.dataBank", dialogue_render_event.get("dataBank") == 0),
            ("render_state.appliedCount", dialogue_render_event.get("groundedControlAppliedCount") == 6),
            (
                "render_state.browserEventVmRenderStateSnapshotImplemented",
                dialogue_render_event.get("browserEventVmRenderStateSnapshotImplemented") is True,
            ),
            (
                "render_state.originalEventVmRuntimeImplemented",
                dialogue_render_event.get("originalEventVmRuntimeImplemented") is False,
            ),
            ("render_layout.originX", dialogue_render_text_layout.get("originX") == 16),
            ("render_layout.originY", dialogue_render_text_layout.get("originY") == 16),
            ("render_layout.cursorX", dialogue_render_text_layout.get("cursorX") == 16),
            ("render_layout.cursorY", dialogue_render_text_layout.get("cursorY") == 16),
            ("render_layout.fontRow", dialogue_render_text_layout.get("fontRow") == 0),
            ("render_style.source", dialogue_render_style.get("source") == "event-vm-display-style-render"),
            ("render_style.styleName", dialogue_render_style.get("styleName") == "default"),
            ("render_style.textColor", dialogue_render_style.get("textColor") == "#f5eec8"),
            ("render_style.headerColor", dialogue_render_style.get("headerColor") == "#9bd4ff"),
            (
                "render_style.browserEventVmDisplayStyleImplemented",
                dialogue_render_style.get("browserEventVmDisplayStyleImplemented") is True,
            ),
            ("object_focus.active", dialogue_object_focus.get("active") is True),
            ("object_focus.x", dialogue_object_focus.get("x") == 144),
            ("object_focus.y", dialogue_object_focus.get("y") == 248),
            ("object_focus.rawX", dialogue_object_focus.get("rawX") == 144),
            ("object_focus.rawY", dialogue_object_focus.get("rawY") == 248),
            ("object_focus.vaHex", dialogue_object_focus.get("vaHex") == "0x0045ee3c"),
            (
                "object_focus.browserEventVmObjectPositionFocusImplemented",
                dialogue_object_focus.get("browserEventVmObjectPositionFocusImplemented") is True,
            ),
            (
                "object_focus.originalEventVmRuntimeImplemented",
                dialogue_object_focus.get("originalEventVmRuntimeImplemented") is False,
            ),
            (
                "object_focus.originalObjectVmRuntimeImplemented",
                dialogue_object_focus.get("originalObjectVmRuntimeImplemented") is False,
            ),
            ("object_focus_render.rendered", dialogue_object_focus_render.get("rendered") is True),
            ("object_focus_render.x", dialogue_object_focus_render.get("x") == 144),
            ("object_focus_render.y", dialogue_object_focus_render.get("y") == 248),
            ("dialogue_vm.browserEventVmFullImplementation", dialogue_chrome_vm.get("browserEventVmFullImplementation") is False),
            ("mobile_before.completeCount", mobile_before.get("completeCount") == 0),
            ("mobile_after.active", mobile_after.get("active") is False),
            ("mobile_after.scene", mobile_after.get("scene") == "map"),
            ("mobile_after.hidden", mobile_after.get("hidden") is False),
            ("mobile_after.text", mobile_after.get("text") == "B"),
            ("mobile_after.completeCount", mobile_after.get("completeCount") == 0),
            ("pointer_advance.advanced", pointer_advance.get("advanced") is True),
            ("pointer_before.blockId", pointer_before.get("blockId") == "event-dialogue-block-005"),
            ("pointer_before.index", pointer_before.get("index") == 0),
            ("pointer_after.blockId", pointer_after.get("blockId") == "event-dialogue-block-005"),
            ("pointer_after.index", pointer_after.get("index") == 1),
            ("pointer_after_latch.active", pointer_after_latch.get("active") is True),
            ("pointer_after_latch.source", pointer_after_latch.get("source") == "canvas-pointer"),
            ("pointer_after_render.source", pointer_after_render_event.get("source") == "branch-execution"),
            ("pointer_after_render.vaHex", pointer_after_render_event.get("vaHex") == "0x0045ee54"),
            ("pointer_after_render.lineIndex", pointer_after_render_event.get("lineIndex") == 1),
            ("pointer_after_render.line", pointer_after_render_event.get("line") == "이곳은 주작의 시련이 있는 곳"),
            ("pointer_after_render.branchStep", pointer_after_render_event.get("branchExecutionStepIndex") == 17),
            ("pointer_after_render.branchDepth", pointer_after_render_event.get("branchExecutionStackDepth") == 1),
            (
                "pointer_after_render.branchSelection",
                pointer_after_render_event.get("browserEventVmBranchRenderSelectionImplemented") is True,
            ),
            ("pointer_after_render.originX", pointer_after_render_layout.get("originX") == 16),
            ("pointer_after_render.originY", pointer_after_render_layout.get("originY") == 16),
            ("pointer_advance.releaseCleared", pointer_advance.get("releaseCleared") is True),
            ("pointer_release_after.index", pointer_release_after.get("index") == 1),
            ("pointer_release_latch.active", pointer_release_latch.get("active") is False),
            ("pointer_release_latch.released", pointer_release_latch.get("released") is True),
            ("pointer_release_latch.releaseCode", pointer_release_latch.get("releaseCode") == "pointerup:53"),
            ("pointer_sound_counts.dialogue", pointer_sound_counts.get("dialogue") == 1),
            ("pointer_advance_sound.source", pointer_advance_last.get("source") == "dialogue-advance-sound"),
            ("pointer_advance_sound.blockId", pointer_advance_last.get("blockId") == "event-dialogue-block-005"),
            ("pointer_advance_sound.lineIndexBefore", pointer_advance_last.get("lineIndexBefore") == 0),
            ("pointer_advance_sound.lineIndexAfter", pointer_advance_last.get("lineIndexAfter") == 1),
            ("pointer_advance_sound.lineCount", pointer_advance_last.get("lineCount") == 13),
            ("pointer_advance_sound.soundKey", pointer_advance_last.get("soundKey") == "dialogue"),
            ("pointer_advance_sound.soundSrc", pointer_advance_last.get("soundSrc") == "../extract_wlk/01.wav"),
            (
                "pointer_advance_sound.browserDialogueAdvanceSoundImplemented",
                pointer_advance_last.get("browserDialogueAdvanceSoundImplemented") is True,
            ),
            (
                "pointer_advance_sound.originalEventVmRuntimeImplemented",
                pointer_advance_last.get("originalEventVmRuntimeImplemented") is False,
            ),
            (
                "pointer_advance_sound.originalStoryFlagRuntimeImplemented",
                pointer_advance_last.get("originalStoryFlagRuntimeImplemented") is False,
            ),
            ("keyboard_latch.firstAdvanced", keyboard_latch.get("firstAdvanced") is True),
            ("keyboard_latch.repeatBlocked", keyboard_latch.get("repeatBlocked") is True),
            ("keyboard_latch.releaseCleared", keyboard_latch.get("releaseCleared") is True),
            ("keyboard_latch.secondAdvancedAfterRelease", keyboard_latch.get("secondAdvancedAfterRelease") is True),
            ("keyboard_before.index", keyboard_before.get("index") == 1),
            ("keyboard_first.index", keyboard_first.get("index") == 2),
            ("keyboard_repeat.index", keyboard_repeat.get("index") == 2),
            ("keyboard_release.index", keyboard_release.get("index") == 2),
            ("keyboard_second.index", keyboard_second.get("index") == 3),
            ("keyboard_first_latch.active", keyboard_first_latch.get("active") is True),
            ("keyboard_first_latch.waitingForRelease", keyboard_first_latch.get("waitingForRelease") is True),
            (
                "keyboard_first_latch.browserDialogueInputWaitLatchImplemented",
                keyboard_first_latch.get("browserDialogueInputWaitLatchImplemented") is True,
            ),
            (
                "keyboard_first_latch.originalEventVmRuntimeImplemented",
                keyboard_first_latch.get("originalEventVmRuntimeImplemented") is False,
            ),
            ("keyboard_repeat_latch.blockedRepeat", keyboard_repeat_latch.get("blockedRepeat") is True),
            ("keyboard_repeat_latch.advanced", keyboard_repeat_latch.get("advanced") is False),
            ("keyboard_release_latch.active", keyboard_release_latch.get("active") is False),
            ("keyboard_release_latch.released", keyboard_release_latch.get("released") is True),
            ("keyboard_release_latch.releaseCode", keyboard_release_latch.get("releaseCode") == "Enter"),
            ("keyboard_second_latch.active", keyboard_second_latch.get("active") is True),
            ("keyboard_first_counts.dialogue", keyboard_first_counts.get("dialogue") == 2),
            ("keyboard_repeat_counts.dialogue", keyboard_repeat_counts.get("dialogue") == 2),
            ("keyboard_second_counts.dialogue", keyboard_second_counts.get("dialogue") == 3),
            ("keyboard_first_advance_sound.lineIndexBefore", keyboard_first_advance_last.get("lineIndexBefore") == 1),
            ("keyboard_first_advance_sound.lineIndexAfter", keyboard_first_advance_last.get("lineIndexAfter") == 2),
            ("keyboard_repeat_advance_sound.lineIndexBefore", keyboard_repeat_advance_last.get("lineIndexBefore") == 1),
            ("keyboard_repeat_advance_sound.lineIndexAfter", keyboard_repeat_advance_last.get("lineIndexAfter") == 2),
            ("keyboard_second_advance_sound.lineIndexBefore", keyboard_second_advance_last.get("lineIndexBefore") == 2),
            ("keyboard_second_advance_sound.lineIndexAfter", keyboard_second_advance_last.get("lineIndexAfter") == 3),
            ("dialogue_sound_counts.dialogue", dialogue_sound_counts.get("dialogue") == 13),
            ("dialogue_sound_last.key", dialogue_sound_last.get("key") == "dialogue"),
            ("dialogue_sound_last.src", dialogue_sound_last.get("src") == "../extract_wlk/01.wav"),
            ("dialogue_advance_log.count", len(dialogue_advance_log) == 13),
            ("dialogue_advance_first.lineIndexBefore", dialogue_advance_first.get("lineIndexBefore") == 0),
            ("dialogue_advance_first.lineIndexAfter", dialogue_advance_first.get("lineIndexAfter") == 1),
            ("dialogue_advance_last.source", dialogue_advance_last.get("source") == "dialogue-advance-sound"),
            ("dialogue_advance_last.blockId", dialogue_advance_last.get("blockId") == "event-dialogue-block-005"),
            ("dialogue_advance_last.lineIndexBefore", dialogue_advance_last.get("lineIndexBefore") == 12),
            ("dialogue_advance_last.lineIndexAfter", dialogue_advance_last.get("lineIndexAfter") == 13),
            ("dialogue_advance_last.soundSrc", dialogue_advance_last.get("soundSrc") == "../extract_wlk/01.wav"),
            ("saved_counts.dialogue-complete", saved_counts.get("dialogue-complete") == 1),
            ("feedback.source", feedback.get("source") == "dialogue-completion-feedback"),
            ("feedback.text", feedback.get("text") == "대사 완료 1/2"),
            ("feedback.blockId", feedback.get("blockId") == "event-dialogue-block-005"),
            ("feedback.completedCount", feedback.get("completedCount") == 1),
            ("feedback.count", feedback.get("count") == 2),
            ("feedback.remainingCount", feedback.get("remainingCount") == 1),
            ("feedback.nextBlockId", feedback.get("nextBlockId") == "event-dialogue-block-026"),
            ("feedback.durationMs", feedback.get("durationMs") == 1000),
            ("feedback.dialogueCompletionSound", feedback.get("dialogueCompletionSound") == "dialogue"),
            (
                "feedback.dialogueCompletionSoundSrc",
                feedback.get("dialogueCompletionSoundSrc") == "../extract_wlk/01.wav",
            ),
            ("feedback.dialogueCompletionSoundPlayed", feedback.get("dialogueCompletionSoundPlayed") is True),
            (
                "feedback.browserDialogueCompletionFeedbackImplemented",
                feedback.get("browserDialogueCompletionFeedbackImplemented") is True,
            ),
            (
                "feedback.originalEventVmRuntimeImplemented",
                feedback.get("originalEventVmRuntimeImplemented") is False,
            ),
            (
                "feedback.originalStoryFlagRuntimeImplemented",
                feedback.get("originalStoryFlagRuntimeImplemented") is False,
            ),
            ("feedback_render.active", feedback_render.get("active") is True),
            ("feedback_render.text", feedback_render.get("text") == "대사 완료 1/2"),
            ("feedback_render.blockId", feedback_render.get("blockId") == "event-dialogue-block-005"),
            ("feedback_render.completedCount", feedback_render.get("completedCount") == 1),
            ("feedback_render.count", feedback_render.get("count") == 2),
            ("feedback_render.remainingCount", feedback_render.get("remainingCount") == 1),
            ("feedback_render.durationMs", feedback_render.get("durationMs") == 1000),
            ("feedback_render.dialogueCompletionSound", feedback_render.get("dialogueCompletionSound") == "dialogue"),
            (
                "feedback_render.dialogueCompletionSoundSrc",
                feedback_render.get("dialogueCompletionSoundSrc") == "../extract_wlk/01.wav",
            ),
            (
                "feedback_render.dialogueCompletionSoundPlayed",
                feedback_render.get("dialogueCompletionSoundPlayed") is True,
            ),
            (
                "feedback_render.browserDialogueCompletionFeedbackImplemented",
                feedback_render.get("browserDialogueCompletionFeedbackImplemented") is True,
            ),
            ("action_prompt.kind", action_prompt.get("kind") == "dialogue-candidate"),
            ("action_prompt.blockId", action_prompt.get("blockId") == "event-dialogue-block-026"),
            ("action_prompt.text", action_prompt.get("text") == "Enter -> 대사 2/2"),
        ]
        failed_completion_checks = [name for name, ok in completion_checks if not ok]
        if failed_completion_checks:
            raise WebDriverError(
                "candidate dialogue completion save state is incomplete "
                f"({', '.join(failed_completion_checks)}): {state!r}"
            )


def verify_all_completion_state(state: dict) -> None:
    completion = state.get("completion") or {}
    dialogue = completion.get("dialogue") or {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    saved_progress = state.get("savedProgress") or {}
    saved_counts = saved_progress.get("counts") or {}
    auto_save = state.get("autoSave") or {}
    auto_save_counts = ((auto_save.get("progress") or {}).get("counts") or {})
    auto_save_tile = auto_save.get("payloadTile") or {}
    labels = state.get("labels") or []
    notice = state.get("completionNotice") or {}
    notice_block = state.get("completionNoticeBlock") or {}
    progress_after_action = state.get("progressAfterCompletedAction") or {}
    objective_before = state.get("objectiveBefore") or {}
    objective_action = state.get("objectiveAction") or {}
    objective_notice = state.get("objectiveCompletionNotice") or {}
    objective_dialogue = state.get("objectiveActiveDialogueBlock") or {}
    if (
        state.get("scene") != "map"
        or state.get("map") != "map4_08n"
        or state.get("saved") is not True
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "dialogue-complete"
        or auto_save.get("blockId") != "event-dialogue-block-026"
        or auto_save.get("payloadMap") != "map4_08n"
        or auto_save_tile.get("x") != 11
        or auto_save_tile.get("y") != 11
        or auto_save_counts.get("dialogue-complete") != 2
        or auto_save.get("originalEventVmRuntimeImplemented") is not False
        or auto_save.get("originalStoryFlagRuntimeImplemented") is not False
        or state.get("openedId") != "event-dialogue-block-026"
        or state.get("buttonText") != "대사 완료 2"
        or "대사 선택 2" not in labels
        or dialogue.get("count") != 2
        or dialogue.get("completed") is not True
        or dialogue.get("completedCount") != 2
        or dialogue.get("remainingCount") != 0
        or "event-dialogue-block-005" not in (dialogue.get("completedIds") or [])
        or "event-dialogue-block-026" not in (dialogue.get("completedIds") or [])
        or state.get("nextCandidateId") != "event-dialogue-block-026"
        or dialogue.get("originalEventVmRuntimeImplemented") is not False
        or dialogue.get("originalStoryFlagRuntimeImplemented") is not False
        or completion.get("originalStoryFlagRuntimeImplemented") is not False
        or counts.get("dialogue-complete") != 2
        or saved_counts.get("dialogue-complete") != 2
        or progress.get("originalStoryFlagRuntimeImplemented") is not False
        or state.get("completedActionResult") is not True
        or notice.get("blockId") != "dialogue-complete:map4_08n"
        or notice.get("map") != "map4_08n"
        or "대사 완료 2/2" not in " ".join(notice.get("lines") or [])
        or (notice.get("completion") or {}).get("completed") is not True
        or notice.get("originalEventVmRuntimeImplemented") is not False
        or notice.get("originalStoryFlagRuntimeImplemented") is not False
        or notice_block.get("blockId") != "dialogue-complete:map4_08n"
        or notice_block.get("line") != "대사 완료 2/2"
        or notice_block.get("lineCount", 0) < 3
        or objective_before.get("title") != "후보 대사 완료 map4_08n"
        or objective_before.get("nextAction") != "완료 알림 확인"
        or objective_before.get("detail") != "대사 완료 2/2"
        or objective_before.get("source") != "prototype-dialogue-completion"
        or state.get("objectiveResult") is not True
        or objective_action.get("action") != "dialogue-completion-notice"
        or objective_action.get("activeId") != "dialogue-complete:map4_08n"
        or objective_notice.get("blockId") != "dialogue-complete:map4_08n"
        or objective_notice.get("map") != "map4_08n"
        or objective_dialogue.get("blockId") != "dialogue-complete:map4_08n"
        or objective_dialogue.get("line") != "대사 완료 2/2"
        or objective_dialogue.get("lineCount", 0) < 3
        or state.get("menuDirectDialogueIndex") != -1
        or state.get("menuDialogueName") != "대사 선택 2"
        or state.get("menuDialogueResult") is not True
        or state.get("menuModeAfterOpen") != "dialogue-candidate"
        or state.get("menuOpenAfterOpen") is not True
        or state.get("menuCandidateCount") != 2
        or "완료 1/2" not in " ".join(state.get("menuCandidateNames") or [])
        or "완료 2/2" not in " ".join(state.get("menuCandidateNames") or [])
        or (progress_after_action.get("counts") or {}).get("dialogue-complete") != 2
    ):
        raise WebDriverError(f"candidate dialogue all-completion state is incomplete: {state!r}")
    verify_story_flags(
        state,
        expected_count=2,
        expected_blocks=["event-dialogue-block-005", "event-dialogue-block-026"],
        require_saved=True,
    )
    verify_dialogue_completion_notice_feedback(state, "all-completion objective")


def verify_title_continue_restore_state(state: dict) -> None:
    completion = state.get("completion") or {}
    dialogue = completion.get("dialogue") or {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    labels = state.get("labels") or []
    title_map = state.get("titleContinueMap") or {}
    notice = state.get("completionNotice") or {}
    notice_block = state.get("completionNoticeBlock") or {}
    objective_before = state.get("objectiveBefore") or {}
    objective_action = state.get("objectiveAction") or {}
    objective_notice = state.get("objectiveCompletionNotice") or {}
    objective_dialogue = state.get("objectiveActiveDialogueBlock") or {}
    foot = state.get("foot") or {}
    if (
        state.get("titleContinue") is not True
        or state.get("scene") != "map"
        or state.get("map") != "map4_08n"
        or foot.get("x") != 11
        or foot.get("y") != 11
        or title_map.get("quickLoadText") != "임시 불러오기"
        or title_map.get("search") != "?map=map4_08n&startTile=11%2C11"
        or state.get("dialogueButtonText") != "대사 완료 2"
        or "대사 선택 2" not in labels
        or dialogue.get("count") != 2
        or dialogue.get("completed") is not True
        or dialogue.get("completedCount") != 2
        or dialogue.get("remainingCount") != 0
        or "event-dialogue-block-005" not in (dialogue.get("completedIds") or [])
        or "event-dialogue-block-026" not in (dialogue.get("completedIds") or [])
        or dialogue.get("originalEventVmRuntimeImplemented") is not False
        or dialogue.get("originalStoryFlagRuntimeImplemented") is not False
        or completion.get("originalStoryFlagRuntimeImplemented") is not False
        or counts.get("dialogue-complete") != 2
        or progress.get("originalStoryFlagRuntimeImplemented") is not False
        or state.get("dialogueButtonResult") is not True
        or notice.get("blockId") != "dialogue-complete:map4_08n"
        or notice.get("map") != "map4_08n"
        or "대사 완료 2/2" not in " ".join(notice.get("lines") or [])
        or (notice.get("completion") or {}).get("completed") is not True
        or notice.get("originalEventVmRuntimeImplemented") is not False
        or notice.get("originalStoryFlagRuntimeImplemented") is not False
        or notice_block.get("blockId") != "dialogue-complete:map4_08n"
        or notice_block.get("line") != "대사 완료 2/2"
        or notice_block.get("lineCount", 0) < 3
        or objective_before.get("title") != "후보 대사 완료 map4_08n"
        or objective_before.get("nextAction") != "완료 알림 확인"
        or objective_before.get("detail") != "대사 완료 2/2"
        or objective_before.get("source") != "prototype-dialogue-completion"
        or state.get("objectiveResult") is not True
        or objective_action.get("action") != "dialogue-completion-notice"
        or objective_action.get("activeId") != "dialogue-complete:map4_08n"
        or objective_notice.get("blockId") != "dialogue-complete:map4_08n"
        or objective_notice.get("map") != "map4_08n"
        or objective_dialogue.get("blockId") != "dialogue-complete:map4_08n"
        or objective_dialogue.get("line") != "대사 완료 2/2"
        or objective_dialogue.get("lineCount", 0) < 3
        or state.get("menuDirectDialogueIndex") != -1
        or state.get("menuDialogueName") != "대사 선택 2"
        or state.get("menuDialogueResult") is not True
        or state.get("menuModeAfterOpen") != "dialogue-candidate"
        or state.get("menuOpenAfterOpen") is not True
        or state.get("menuCandidateCount") != 2
        or "완료 1/2" not in " ".join(state.get("menuCandidateNames") or [])
        or "완료 2/2" not in " ".join(state.get("menuCandidateNames") or [])
    ):
        raise WebDriverError(f"candidate dialogue title continue restore is incomplete: {state!r}")
    verify_story_flags(
        state,
        expected_count=2,
        expected_blocks=["event-dialogue-block-005", "event-dialogue-block-026"],
        require_saved=False,
    )
    verify_dialogue_completion_notice_feedback(state, "title-continue objective")


def verify_display_style_probe_state(state: dict) -> None:
    style = state.get("displayStyle") or {}
    text_layout = state.get("textLayout") or {}
    render_event = state.get("renderEvent") or {}
    render_layout = render_event.get("controlTextLayout") or {}
    render_style = render_event.get("displayStyleRender") or {}
    display_timer = style.get("displayTimerState") or {}
    branch_font = state.get("branchFontRowFirst") or {}
    branch_style = state.get("branchDisplayStyleFirst") or {}
    branch_style_render = branch_style.get("displayStyleRender") or {}
    branch_object = state.get("branchObjectPositionFirst") or {}
    branch_display_timer = state.get("branchDisplayTimerFirst") or {}
    branch_display_timer_state = branch_display_timer.get("displayTimerState") or {}
    if (
        state.get("active") is not True
        or state.get("blockId") != "event-dialogue-block-029"
        or state.get("header") != "대화 1/15"
        or state.get("lineCount") != 15
        or state.get("eventCount") != 21
        or state.get("opcodeSet") != ["0x03", "0x04", "0x07", "0x08", "0x09", "0x0a"]
        or state.get("branchFontRowEventCount") != 12
        or state.get("branchFontRowLineCount") != 12
        or state.get("browserEventVmBranchFontRowImplemented") is not True
        or branch_font.get("vaHex") != "0x004ec78a"
        or branch_font.get("fontRow") != 2
        or branch_font.get("lineIndex") != 0
        or state.get("branchDisplayStyleEventCount") != 12
        or state.get("branchDisplayStyleLineCount") != 12
        or state.get("browserEventVmBranchDisplayStyleImplemented") is not True
        or branch_style.get("vaHex") != "0x004ec78e"
        or branch_style.get("tableIndexByte") != 1
        or branch_style_render.get("styleName") != "emphasis"
        or branch_style.get("browserEventVmBranchDisplayStyleImplemented") is not True
        or state.get("branchObjectPositionEventCount") != 12
        or state.get("branchObjectPositionLineCount") != 12
        or state.get("browserEventVmBranchObjectPositionImplemented") is not True
        or branch_object.get("vaHex") != "0x004ec79a"
        or branch_object.get("x") != 144
        or branch_object.get("y") != 248
        or branch_object.get("browserEventVmBranchObjectPositionImplemented") is not True
        or state.get("branchDisplayTimerEventCount") != 12
        or state.get("branchDisplayTimerLineCount") != 12
        or state.get("browserEventVmBranchDisplayTimerImplemented") is not True
        or branch_display_timer.get("vaHex") != "0x004ec7a2"
        or branch_display_timer.get("lineIndex") != 0
        or branch_display_timer.get("rawOperandWord4") != 2880
        or branch_display_timer_state.get("rawOperandWord4") != 2880
        or branch_display_timer.get("browserEventVmBranchDisplayTimerImplemented") is not True
        or text_layout.get("fontRow") != 2
        or text_layout.get("styleTableIndex") != 1
        or state.get("renderStateSnapshotCount", 0) < 1
        or state.get("browserEventVmRenderStateSnapshotImplemented") is not True
        or render_event.get("lineIndex") != 0
        or render_event.get("browserEventVmRenderStateSnapshotImplemented") is not True
        or render_layout.get("fontRow") != 2
        or render_layout.get("styleTableIndex") != 1
        or render_style.get("styleName") != "emphasis"
        or render_style.get("browserEventVmDisplayStyleImplemented") is not True
        or style.get("source") != "event-vm-display-style-render"
        or style.get("fontRow") != 2
        or style.get("styleMode") != 0
        or style.get("styleTableIndex") != 1
        or style.get("styleName") != "emphasis"
        or style.get("textColor") != "#ffe07a"
        or style.get("headerColor") != "#ffd447"
        or style.get("fontPx") != 15
        or style.get("lineHeight") != 22
        or display_timer.get("seed") != 0
        or display_timer.get("rawOperandWord4") != 2880
        or style.get("globalTimerState") is not None
        or style.get("timerSeed") != 0
        or style.get("browserEventVmDisplayStyleImplemented") is not True
        or style.get("browserEventVmDisplayTimerStateImplemented") is not True
        or style.get("originalEventVmRuntimeImplemented") is not False
        or state.get("browserEventVmFullImplementation") is not False
        or state.get("originalEventVmRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate dialogue display style probe is incomplete: {state!r}")


def verify_cursor_relative_probe_state(state: dict) -> None:
    render_event = state.get("renderEvent") or {}
    cursor_layout = state.get("cursorLayout") or {}
    render_layout = render_event.get("controlTextLayout") or {}
    render_style = render_event.get("displayStyleRender") or {}
    branch_cursor = state.get("branchCursorRelativeCurrent") or {}
    branch_cursor_layout = branch_cursor.get("cursorLayout") or {}
    branch_font = state.get("branchFontRowFirst") or {}
    branch_style = state.get("branchDisplayStyleFirst") or {}
    branch_object = state.get("branchObjectPositionFirst") or {}
    branch_data_bank = state.get("branchDataBankFirst") or {}
    branch_display_timer = state.get("branchDisplayTimerFirst") or {}
    branch_display_timer_state = branch_display_timer.get("displayTimerState") or {}
    if (
        state.get("active") is not True
        or state.get("blockId") != "event-dialogue-block-028"
        or state.get("header") != "대화 2/65"
        or state.get("lineCount") != 65
        or state.get("index") != 1
        or state.get("line") != "기본기"
        or state.get("groundedControlCursorRelativeCount") != 37
        or state.get("cursorRelativeLineLayoutCount") != 20
        or state.get("browserEventVmCursorRelativeLayoutImplemented") is not True
        or state.get("branchCursorRelativeEventCount") != 37
        or state.get("branchCursorRelativeLineCount") != 37
        or state.get("browserEventVmBranchCursorRelativeImplemented") is not True
        or state.get("branchFontRowEventCount") != 6
        or state.get("browserEventVmBranchFontRowImplemented") is not True
        or branch_font.get("vaHex") != "0x004e7fec"
        or branch_font.get("fontRow") != 0
        or state.get("branchDisplayStyleEventCount") != 6
        or state.get("browserEventVmBranchDisplayStyleImplemented") is not True
        or branch_style.get("vaHex") != "0x004e7ff0"
        or branch_style.get("tableIndexByte") != 0
        or state.get("branchObjectPositionEventCount") != 5
        or state.get("browserEventVmBranchObjectPositionImplemented") is not True
        or branch_object.get("vaHex") != "0x004e8000"
        or branch_object.get("x") != 0
        or branch_object.get("y") != 352
        or state.get("branchDataBankEventCount") != 4
        or state.get("browserEventVmBranchDataBankImplemented") is not True
        or branch_data_bank.get("vaHex") != "0x004e8088"
        or branch_data_bank.get("dataBank") != 0
        or branch_cursor.get("vaHex") != "0x004e8054"
        or branch_cursor.get("rawX") != 24
        or branch_cursor.get("rawY") != 96
        or branch_cursor_layout.get("browserEventVmCursorRelativeLayoutImplemented") is not True
        or state.get("branchDisplayTimerEventCount") != 5
        or state.get("browserEventVmBranchDisplayTimerImplemented") is not True
        or branch_display_timer.get("vaHex") != "0x004e7ffc"
        or branch_display_timer.get("rawOperandWord4") != 1856
        or branch_display_timer_state.get("rawOperandWord4") != 1856
        or render_event.get("lineIndex") != state.get("index")
        or render_event.get("line") != state.get("line")
        or render_event.get("browserEventVmRenderStateSnapshotImplemented") is not True
        or render_event.get("groundedControlAppliedCount") != 14
        or render_layout.get("originX") != 14
        or render_layout.get("originY") != 34
        or render_layout.get("cursorX") != 38
        or render_layout.get("cursorY") != 130
        or render_style.get("styleName") != "default"
        or render_style.get("browserEventVmDisplayStyleImplemented") is not True
        or cursor_layout.get("browserEventVmCursorRelativeLayoutImplemented") is not True
        or cursor_layout.get("rawX") != 24
        or cursor_layout.get("rawY") != 96
        or cursor_layout.get("relativeX") != 24
        or cursor_layout.get("relativeY") != 96
        or cursor_layout.get("x") != 38
        or cursor_layout.get("y") != 130
        or cursor_layout.get("originalEventVmRuntimeImplemented") is not False
        or state.get("browserEventVmFullImplementation") is not False
        or state.get("originalEventVmRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate dialogue cursor-relative probe is incomplete: {state!r}")


def verify_literal_payload_probe_state(state: dict) -> None:
    first_render = state.get("firstRenderEvent") or {}
    first_literal = first_render.get("literalTextEvent") or {}
    line_advance = state.get("literalLineAdvance") or {}
    line_advance_literal = line_advance.get("literalTextEvent") or {}
    line_advance_render = state.get("lineAdvanceRenderEvent") or {}
    line_advance_render_literal = line_advance_render.get("literalTextEvent") or {}
    if (
        state.get("active") is not True
        or state.get("blockId") != "event-dialogue-block-001"
        or state.get("header") != "대화 1/15"
        or state.get("lineCount") != 15
        or state.get("literalTextEventCount") != 7
        or (state.get("literalTextSamples") or [])[:3] != ["다리오스", "황금돼지야", "도대체 어디 있는거니"]
        or state.get("browserEventVmLiteralTextPayloadLineImplemented") is not True
        or state.get("branchLiteralTextPayloadEventCount", 0) < 1
        or state.get("branchLiteralTextPayloadExecutionCount", 0) < 1
        or state.get("branchLiteralTextMatchedLineCount", 0) < 2
        or "0x35" not in (state.get("branchExecutedOpcodeSet") or [])
        or first_render.get("source") != "branch-execution"
        or first_render.get("lineIndex") != 0
        or first_render.get("line") != "다리오스"
        or first_render.get("displayedLine") != "다리오스"
        or first_render.get("candidateLine") != "다리오스"
        or first_render.get("lineSource") != "literal-cp949-text-payload"
        or first_render.get("browserEventVmLiteralTextPayloadLineImplemented") is not True
        or first_literal.get("vaHex") != "0x00441737"
        or first_literal.get("text") != "다리오스"
        or line_advance.get("lineIndex") != 2
        or line_advance.get("line") != "도대체 어디 있는거니"
        or line_advance.get("lineSource") != "literal-cp949-text-payload"
        or line_advance.get("browserEventVmLiteralTextPayloadLineImplemented") is not True
        or line_advance_literal.get("vaHex") != "0x00441754"
        or line_advance_render.get("source") != "branch-execution"
        or line_advance_render.get("line") != "도대체 어디 있는거니"
        or line_advance_render.get("displayedLine") != "도대체 어디 있는거니"
        or line_advance_render.get("lineSource") != "literal-cp949-text-payload"
        or line_advance_render.get("browserEventVmLiteralTextPayloadLineImplemented") is not True
        or line_advance_render_literal.get("vaHex") != "0x00441754"
        or state.get("browserEventVmFullImplementation") is not False
        or state.get("originalEventVmRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate dialogue literal payload probe is incomplete: {state!r}")


def verify_dialogue_completion_notice_feedback(state: dict, context: str) -> None:
    feedback_log = state.get("objectiveDialogueCompletionFeedbackLog") or []
    feedback_render_log = state.get("objectiveDialogueCompletionFeedbackRender") or []
    feedback = state.get("objectiveDialogueCompletionFeedbackLast") or {}
    feedback_render = state.get("objectiveDialogueCompletionFeedbackLastRender") or {}
    if (
        len(feedback_log) < 1
        or len(feedback_render_log) < 1
        or feedback.get("source") != "dialogue-completion-notice-feedback"
        or feedback.get("text") != "대사 완료 2/2"
        or feedback.get("map") != "map4_08n"
        or feedback.get("blockId") != "dialogue-complete:map4_08n"
        or feedback.get("completedCount") != 2
        or feedback.get("count") != 2
        or feedback.get("remainingCount") != 0
        or feedback.get("dialogueCompletionSound") != "menuConfirm"
        or not str(feedback.get("dialogueCompletionSoundSrc") or "").endswith("/extract_wlk/04.wav")
        or feedback.get("dialogueCompletionSoundPlayed") is not True
        or feedback.get("durationMs") != 1000
        or feedback.get("browserDialogueCompletionFeedbackImplemented") is not True
        or feedback.get("originalEventVmRuntimeImplemented") is not False
        or feedback.get("originalStoryFlagRuntimeImplemented") is not False
        or feedback_render.get("active") is not True
        or feedback_render.get("source") != "dialogue-completion-notice-feedback"
        or feedback_render.get("text") != "대사 완료 2/2"
        or feedback_render.get("map") != "map4_08n"
        or feedback_render.get("blockId") != "dialogue-complete:map4_08n"
        or feedback_render.get("completedCount") != 2
        or feedback_render.get("count") != 2
        or feedback_render.get("remainingCount") != 0
        or feedback_render.get("dialogueCompletionSound") != "menuConfirm"
        or not str(feedback_render.get("dialogueCompletionSoundSrc") or "").endswith("/extract_wlk/04.wav")
        or feedback_render.get("dialogueCompletionSoundPlayed") is not True
        or feedback_render.get("durationMs") != 1000
        or feedback_render.get("browserDialogueCompletionFeedbackImplemented") is not True
        or feedback_render.get("originalEventVmRuntimeImplemented") is not False
        or feedback_render.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"{context} dialogue completion notice feedback is incomplete: {state!r}")


def dialogue_completion_notice_feedback_summary(state: dict) -> str:
    feedback = state.get("objectiveDialogueCompletionFeedbackLast") or {}
    rendered = state.get("objectiveDialogueCompletionFeedbackLastRender") or {}
    return (
        f"noticeFeedback={feedback.get('source')}:{feedback.get('text')} "
        f"noticeFeedbackRender={rendered.get('active')} "
        f"noticeFeedbackSound={feedback.get('dialogueCompletionSound')}:"
        f"{feedback.get('dialogueCompletionSoundSrc')}:{feedback.get('dialogueCompletionSoundPlayed')}"
    )


def story_flags_summary(state: dict) -> str:
    story_flags = state.get("storyFlags") or {}
    progress_story_flags = ((state.get("progress") or {}).get("storyFlags") or {})
    saved_story_flags = state.get("savedStoryFlags") or {}
    flag_keys = ",".join(sorted((story_flags.get("flags") or {}).keys())) or "-"
    saved_count = saved_story_flags.get("flagCount") if saved_story_flags else "-"
    return (
        f"storyFlags={story_flags.get('flagCount')} "
        f"progressStoryFlags={progress_story_flags.get('flagCount')} "
        f"savedStoryFlags={saved_count} "
        f"storyFlagKeys={flag_keys} "
        f"storyFlagSource={story_flags.get('source')} "
        f"browserPrototypeStoryFlagImplemented={story_flags.get('browserPrototypeStoryFlagImplemented')} "
        f"originalStoryFlagRuntimeImplemented={story_flags.get('originalStoryFlagRuntimeImplemented')}"
    )


def write_report(report: dict) -> None:
    out_dir = ROOT / "out"
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "candidate_dialogue_progress_browser_smoke.json").write_text(
        json.dumps(report, ensure_ascii=False, separators=(",", ":")) + "\n",
        encoding="utf-8",
    )
    lines = [
        "# Candidate Dialogue Progress Browser Smoke",
        "",
	        f"- status: `{report.get('status')}`",
        f"- dialogue candidate menu: `{report.get('dialogueCandidateMenu')}`",
	        f"- dialogue prompt priority: `{report.get('dialoguePromptPriority')}`",
	        f"- completed dialogue: `{report.get('completedDialogue')}`",
	        f"- mobile dialogue cancel: `{report.get('mobileDialogueCancel')}`",
	        f"- dialogue chrome: `{report.get('dialogueChrome')}`",
	        f"- dialogue control flow: `{report.get('dialogueControlFlow')}`",
	        f"- dialogue render state: `{report.get('dialogueRenderState')}`",
	        f"- dialogue display style: `{report.get('dialogueDisplayStyle')}`",
	        f"- dialogue display style probe: `{report.get('dialogueDisplayStyleProbe')}`",
	        f"- dialogue cursor relative layout: `{report.get('dialogueCursorRelativeLayout')}`",
	        f"- dialogue literal payload: `{report.get('dialogueLiteralPayload')}`",
	        f"- dialogue object focus: `{report.get('dialogueObjectFocus')}`",
	        f"- pointer dialogue advance: `{report.get('pointerDialogueAdvance')}`",
	        f"- keyboard input wait latch: `{report.get('keyboardInputWaitLatch')}`",
	        f"- restored dialogue: `{report.get('restoredDialogue')}`",
	        f"- all completed dialogue: `{report.get('allCompletedDialogue')}`",
	        f"- title restored dialogue: `{report.get('titleRestoredDialogue')}`",
        "",
    ]


def verify_browser(base: str) -> None:
    driver_path = shutil.which("WebKitWebDriver")
    if not driver_path:
        raise WebDriverError("WebKitWebDriver is not installed; install webkit2gtk-driver and run under Xvfb")

    port = free_port()
    log_path = ROOT / "out" / "candidate_dialogue_progress_webkitdriver.log"
    log_path.parent.mkdir(parents=True, exist_ok=True)
    with log_path.open("wb") as log:
        proc = subprocess.Popen(
            [
                driver_path,
                "--host=127.0.0.1",
                f"--port={port}",
                "--replace-on-new-session",
            ],
            stdout=log,
            stderr=subprocess.STDOUT,
        )
        session_id = ""
        try:
            wait_for_driver(port, proc)
            session = request_json(
                port,
                "POST",
                "/session",
                {"capabilities": {"alwaysMatch": {"browserName": "MiniBrowser"}}},
                timeout=30,
            )
            session_id = str(session["value"]["sessionId"])
            request_json(
                port,
                "POST",
                f"/session/{session_id}/window/rect",
                {"x": 0, "y": 0, "width": 390, "height": 844},
                timeout=8,
            )

            dialogue_menu_url = load_map(base, port, session_id, {"map": "map4_08n", "startTile": "11,11"})
            execute_js(port, session_id, dialogue_candidate_menu_selection_script(), timeout=3)
            dialogue_menu_state = wait_for_dialogue_candidate_menu_state(port, session_id)
            verify_dialogue_candidate_menu_state(dialogue_menu_state)

            completion_url = load_map(base, port, session_id, {"map": "map4_08n", "startTile": "11,11"})
            execute_js(port, session_id, start_dialogue_completion_script(), timeout=3)
            completion_state = wait_for_completion_state(port, session_id)
            verify_completion_state(completion_state)

            restore_url = load_map(base, port, session_id, {"map": "map4_08n", "startTile": "11,11"})
            execute_js(port, session_id, restore_dialogue_completion_script(), timeout=3)
            restore_state = wait_for_restore_state(port, session_id)
            verify_completion_state(restore_state, restored=True)

            execute_js(port, session_id, complete_all_dialogue_script(), timeout=3)
            all_completion_state = wait_for_all_completion_state(port, session_id)
            verify_all_completion_state(all_completion_state)

            title_url = urljoin(base.rstrip("/") + "/", "/web/game.html?game=1")
            request_json(port, "POST", f"/session/{session_id}/url", {"url": title_url}, timeout=30)
            wait_for_page(port, session_id)
            continue_title = wait_for_continue_ready_title(port, session_id)
            if not any("이어하기 map4_08n 11,11" in str(label) for label in (continue_title.get("titleMenuLabels") or [])):
                raise WebDriverError(f"title continue row did not summarize dialogue quick save: {continue_title!r}")
            title_continue_click = execute_js(port, session_id, click_title_continue_script(), timeout=3)
            if not isinstance(title_continue_click, dict) or title_continue_click.get("ok") is not True:
                raise WebDriverError(f"title continue button was not usable: {title_continue_click!r}")
            continued_dialogue_map = wait_for_continued_dialogue_map(port, session_id)
            execute_js(port, session_id, start_title_continue_restore_script(), timeout=3)
            title_continue_restore = wait_for_title_continue_restore_state(port, session_id)
            verify_title_continue_restore_state(title_continue_restore)

            display_style_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            execute_js(port, session_id, start_dialogue_display_style_probe_script(), timeout=3)
            display_style_probe = wait_for_display_style_probe_state(port, session_id)
            verify_display_style_probe_state(display_style_probe)

            cursor_relative_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            execute_js(port, session_id, start_dialogue_cursor_relative_probe_script(), timeout=3)
            cursor_relative_probe = wait_for_cursor_relative_probe_state(port, session_id)
            verify_cursor_relative_probe_state(cursor_relative_probe)

            literal_payload_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            execute_js(port, session_id, start_dialogue_literal_payload_probe_script(), timeout=3)
            literal_payload_probe = wait_for_literal_payload_probe_state(port, session_id)
            verify_literal_payload_probe_state(literal_payload_probe)

            mobile_cancel_state = completion_state.get("mobileCancel") or {}
            mobile_cancel_before = mobile_cancel_state.get("before") or {}
            mobile_cancel_after = mobile_cancel_state.get("after") or {}
            reopened_dialogue_sound = mobile_cancel_state.get("reopenedSoundState") or {}
            reopened_dialogue_sound_counts = reopened_dialogue_sound.get("counts") or {}
            reopened_dialogue_open_last = reopened_dialogue_sound.get("openLast") or {}
            dialogue_chrome_vm = (mobile_cancel_before.get("dialogueChrome") or {}).get("vmReplay") or {}
            dialogue_display_style = dialogue_chrome_vm.get("groundedControlDisplayStyleRender") or {}
            dialogue_render_event = (mobile_cancel_before.get("dialogueChrome") or {}).get("vmRenderEvent") or {}
            dialogue_render_text_layout = dialogue_render_event.get("controlTextLayout") or {}
            dialogue_render_style = dialogue_render_event.get("displayStyleRender") or {}
            dialogue_branch_flow = dialogue_chrome_vm.get("groundedControlBranchFlow") or {}
            dialogue_branch_execution = dialogue_chrome_vm.get("groundedControlBranchExecution") or {}
            dialogue_branch_first_call = dialogue_branch_flow.get("firstCall") or {}
            dialogue_branch_execution_first_call = dialogue_branch_execution.get("firstCall") or {}
            dialogue_branch_text_origin = dialogue_branch_execution.get("firstTextOriginEvent") or {}
            dialogue_branch_global_timer = dialogue_branch_execution.get("firstGlobalTimerEvent") or {}
            dialogue_object_focus = dialogue_chrome_vm.get("groundedControlObjectFocus") or {}
            dialogue_object_focus_render = dialogue_chrome_vm.get("groundedControlObjectFocusRendered") or {}
            title_continue_label = next(
                (str(label) for label in (continue_title.get("titleMenuLabels") or []) if str(label).startswith("이어하기")),
                "",
            )
            title_continue_map = title_continue_restore.get("titleContinueMap") or {}
            completion_dialogue_sound = completion_state.get("dialogueSoundState") or {}
            completion_dialogue_sound_counts = completion_dialogue_sound.get("counts") or {}
            completion_dialogue_advance_last = completion_dialogue_sound.get("advanceLast") or {}
            pointer_dialogue_sound = ((completion_state.get("pointerAdvance") or {}).get("soundState") or {})
            pointer_dialogue_sound_counts = pointer_dialogue_sound.get("counts") or {}
            pointer_dialogue_advance_last = pointer_dialogue_sound.get("advanceLast") or {}
            pointer_after_state = (completion_state.get("pointerAdvance") or {}).get("after") or {}
            pointer_after_chrome = pointer_after_state.get("dialogueChrome") or {}
            pointer_after_render_event = pointer_after_chrome.get("vmRenderEvent") or {}
            branch_line_advance_probe = completion_state.get("branchLineAdvanceProbe") or {}
            branch_line_advance_render_event = branch_line_advance_probe.get("renderEvent") or {}
            branch_wait_probe = completion_state.get("branchWaitProbe") or {}
            branch_wait_latch = branch_wait_probe.get("latch") or {}
            branch_wait_latch_event = branch_wait_latch.get("branchWaitEvent") or {}
            branch_wait_chrome_vm = (branch_wait_probe.get("chrome") or {}).get("vmReplay") or {}
            branch_wait_chrome_event = branch_wait_chrome_vm.get("groundedControlBranchWaitEvent") or {}
            keyboard_latch = completion_state.get("keyboardInputWaitLatch") or {}
            keyboard_dialogue_sound_first = keyboard_latch.get("soundAfterFirst") or {}
            keyboard_dialogue_sound_repeat = keyboard_latch.get("soundAfterRepeat") or {}
            keyboard_dialogue_sound_second = keyboard_latch.get("soundAfterSecondAdvance") or {}
            keyboard_dialogue_sound_first_counts = keyboard_dialogue_sound_first.get("counts") or {}
            keyboard_dialogue_sound_repeat_counts = keyboard_dialogue_sound_repeat.get("counts") or {}
            keyboard_dialogue_sound_second_counts = keyboard_dialogue_sound_second.get("counts") or {}
            report = {
                "status": "passed",
                "base": base,
                "dialogueMenuUrl": dialogue_menu_url,
                "completionUrl": completion_url,
                "restoreUrl": restore_url,
                "titleUrl": title_url,
                "displayStyleUrl": display_style_url,
                "cursorRelativeUrl": cursor_relative_url,
                "literalPayloadUrl": literal_payload_url,
                "continueTitle": continue_title,
                "titleContinueClick": title_continue_click,
                "continuedDialogueMap": continued_dialogue_map,
                "dialoguePromptPriority": (
                    "map4_08n "
                    f"prompt={(completion_state.get('beforePrompt') or {}).get('text')} "
                    f"kind={(completion_state.get('beforePrompt') or {}).get('kind')} "
                    f"blockId={(completion_state.get('beforePrompt') or {}).get('blockId')} "
                    f"activated={(completion_state.get('promptActivation') or {}).get('activated')} "
                    f"activeBlock={(completion_state.get('promptActivation') or {}).get('blockId')} "
                    "directDialogueBeforeShop=True "
                    "originalEventVmRuntimeImplemented=False"
                ),
                "dialogueCandidateMenu": (
                    f"menuCount={(dialogue_menu_state.get('before') or {}).get('count')} "
                    f"selected={(dialogue_menu_state.get('selection') or {}).get('blockId')} "
                    f"preview={(dialogue_menu_state.get('selection') or {}).get('preview')} "
                    f"active={(dialogue_menu_state.get('activeDialogue') or {}).get('blockId')} "
                    f"lineCount={(dialogue_menu_state.get('activeDialogue') or {}).get('lineCount')} "
                    f"firstLine={(dialogue_menu_state.get('activeDialogue') or {}).get('firstLine')} "
                    f"source={(dialogue_menu_state.get('selection') or {}).get('source')} "
                    f"menuMode={(dialogue_menu_state.get('before') or {}).get('menuMode')} "
                    f"commandResult={dialogue_menu_state.get('commandResult')} "
                    f"afterMenuOpen={dialogue_menu_state.get('afterMenuOpen')} "
                    f"dialogueOpenSound={((dialogue_menu_state.get('dialogueOpenSoundLast') or {}).get('source') or '')}:"
                    f"{((dialogue_menu_state.get('dialogueOpenSoundLast') or {}).get('soundSrc') or '')} "
                    f"vmPartial={(dialogue_menu_state.get('activeDialogue') or {}).get('vmPartial')} "
                    f"vmFull={(dialogue_menu_state.get('activeDialogue') or {}).get('vmFull')} "
                    "originalEventVmRuntimeImplemented=False "
                    "originalStoryFlagRuntimeImplemented=False"
                ),
                "completedDialogue": (
                    "event-dialogue-block-005 "
                    f"button={completion_state.get('buttonText')} "
                    f"next={completion_state.get('nextCandidateId')} "
                    f"dialogueCompleteCount={(completion_state.get('progress') or {}).get('counts', {}).get('dialogue-complete')} "
                    f"{story_flags_summary(completion_state)} "
                    f"dialogueFeedback={((completion_state.get('dialogueFeedbackLast') or {}).get('source') or '')}:"
                    f"{((completion_state.get('dialogueFeedbackLast') or {}).get('text') or '')} "
                    f"dialogueFeedbackRender={bool(completion_state.get('dialogueFeedbackRender') or [])} "
                    f"dialogueFeedbackSound={((completion_state.get('dialogueFeedbackLast') or {}).get('dialogueCompletionSound') or '')}:"
                    f"{((completion_state.get('dialogueFeedbackLast') or {}).get('dialogueCompletionSoundSrc') or '')}:"
                    f"{((completion_state.get('dialogueFeedbackLast') or {}).get('dialogueCompletionSoundPlayed'))} "
                    f"dialogueAdvanceSoundCount={completion_dialogue_sound_counts.get('dialogue')} "
                    f"dialogueAdvanceSoundSrc={completion_dialogue_advance_last.get('soundSrc')} "
                    f"autoSaved={(completion_state.get('autoSave') or {}).get('saved')} "
                    f"autoSource={(completion_state.get('autoSave') or {}).get('source')} "
                    "originalStoryFlagRuntimeImplemented=False"
                ),
                "mobileDialogueCancel": (
                    "event-dialogue-block-005 "
                    f"mobileB={not mobile_cancel_before.get('hidden')} "
                    f"active={mobile_cancel_after.get('active')} "
                    f"completeCount={mobile_cancel_after.get('completeCount')} "
                    f"restarted={mobile_cancel_state.get('restarted')} "
                    f"reopened={mobile_cancel_state.get('reopenedId')} "
                    f"dialogueOpenSoundCount={reopened_dialogue_sound_counts.get('dialogue')} "
                    f"dialogueOpenSoundSrc={reopened_dialogue_open_last.get('soundSrc')}"
                ),
                "dialogueChrome": (
                    "event-dialogue-block-005 "
                    f"header={((mobile_cancel_before.get('dialogueChrome') or {}).get('header'))} "
                    f"internalIdHidden={((mobile_cancel_before.get('dialogueChrome') or {}).get('internalIdHidden'))} "
                    f"groundedControlEffects={dialogue_chrome_vm.get('groundedControlEffectsExecuted')} "
                    f"groundedControlEvents={dialogue_chrome_vm.get('groundedControlExecutedEventCount')} "
                    f"groundedControlOpcodes={','.join(dialogue_chrome_vm.get('groundedControlExecutedOpcodeSet') or [])} "
                    f"groundedControlWaits={dialogue_chrome_vm.get('groundedControlWaitForInputReleaseCount')} "
                    f"groundedControlBranches={dialogue_chrome_vm.get('groundedControlBranchCallCount')} "
                    f"groundedControlObjects={dialogue_chrome_vm.get('groundedControlObjectPositionCount')}"
                ),
	                "dialogueControlFlow": (
                    "event-dialogue-block-005 "
                    f"calls={dialogue_branch_flow.get('callCount')} "
                    f"returns={dialogue_branch_flow.get('returnCount')} "
                    f"maxDepth={dialogue_branch_flow.get('maxDepth')} "
                    f"finalDepth={dialogue_branch_flow.get('finalDepth')} "
                    f"underflowReturns={dialogue_branch_flow.get('underflowReturnCount')} "
                    f"firstTarget={dialogue_branch_first_call.get('branchTargetVaHex')} "
                    f"firstContinuation={dialogue_branch_first_call.get('continuationVaHex')} "
                    f"dataBank={dialogue_chrome_vm.get('groundedControlDataBank')} "
                    f"dataBankSelects={dialogue_chrome_vm.get('groundedControlDataBankSelectCount')} "
                    f"branchExecSteps={dialogue_branch_execution.get('stepCount')} "
                    f"branchExecRenders={dialogue_branch_execution.get('renderEventCount')} "
                    f"branchExecLineAdvances={dialogue_branch_execution.get('lineAdvanceEventCount')} "
                    f"branchExecWaits={dialogue_branch_execution.get('waitEventCount')} "
                    f"branchExecReturns={dialogue_branch_execution.get('returnCount')} "
                    f"branchExecMaxDepth={dialogue_branch_execution.get('maxDepth')} "
                    f"branchExecFinalDepth={dialogue_branch_execution.get('finalDepth')} "
                    f"branchExecHalt={dialogue_branch_execution.get('haltedReason')} "
                    f"branchExecFirstTarget={dialogue_branch_execution_first_call.get('branchTargetVaHex')} "
                    f"branchExecFirstContinuation={dialogue_branch_execution_first_call.get('continuationVaHex')} "
                    f"branchTextOrigin={dialogue_branch_execution.get('textOriginEventCount')}:"
                    f"{dialogue_branch_text_origin.get('vaHex')}:"
                    f"{dialogue_branch_text_origin.get('originX')},"
                    f"{dialogue_branch_text_origin.get('originY')} "
                    f"branchGlobalTimer={dialogue_branch_execution.get('globalTimerEventCount')}:"
                    f"{dialogue_branch_global_timer.get('vaHex')}:"
                    f"{((dialogue_branch_global_timer.get('globalTimerState') or {}).get('seed'))} "
                    "browserEventVmBranchExecutionImplemented=True "
                    "browserEventVmBranchTextOriginImplemented=True "
                    "browserEventVmBranchGlobalTimerImplemented=True "
                    "browserEventVmControlFlowSummaryImplemented=True "
                    "originalEventVmRuntimeImplemented=False"
                ),
                "dialogueRenderState": (
                    "event-dialogue-block-005 "
                    f"snapshots={dialogue_chrome_vm.get('renderStateSnapshotCount')} "
                    f"renderVa={dialogue_render_event.get('vaHex')} "
                    f"lineIndex={dialogue_render_event.get('lineIndex')} "
                    f"textSource={dialogue_render_event.get('textSourceValueHex')} "
                    f"origin={dialogue_render_text_layout.get('originX')},{dialogue_render_text_layout.get('originY')} "
                    f"cursor={dialogue_render_text_layout.get('cursorX')},{dialogue_render_text_layout.get('cursorY')} "
                    f"fontRow={dialogue_render_text_layout.get('fontRow')} "
                    f"style={dialogue_render_style.get('styleName')} "
                    f"dataBank={dialogue_render_event.get('dataBank')} "
                    f"appliedControls={dialogue_render_event.get('groundedControlAppliedCount')} "
                    "browserEventVmRenderStateSnapshotImplemented=True "
                    "originalEventVmRuntimeImplemented=False"
                ),
                "dialogueDisplayStyle": (
                    "event-dialogue-block-005 "
                    f"fontRow={dialogue_display_style.get('fontRow')} "
                    f"styleMode={dialogue_display_style.get('styleMode')} "
                    f"styleTableIndex={dialogue_display_style.get('styleTableIndex')} "
                    f"styleName={dialogue_display_style.get('styleName')} "
                    f"textColor={dialogue_display_style.get('textColor')} "
                    f"headerColor={dialogue_display_style.get('headerColor')} "
                    f"fontPx={dialogue_display_style.get('fontPx')} "
                    f"lineHeight={dialogue_display_style.get('lineHeight')} "
                    f"timerSeed={dialogue_display_style.get('timerSeed')} "
                    "browserEventVmDisplayStyleImplemented=True "
                    "browserEventVmDisplayTimerStateImplemented=True "
                    "originalEventVmRuntimeImplemented=False"
                ),
                "dialogueDisplayStyleProbe": (
                    "event-dialogue-block-029 "
                    f"fontRow={(display_style_probe.get('displayStyle') or {}).get('fontRow')} "
                    f"styleTableIndex={(display_style_probe.get('displayStyle') or {}).get('styleTableIndex')} "
                    f"styleName={(display_style_probe.get('displayStyle') or {}).get('styleName')} "
                    f"textColor={(display_style_probe.get('displayStyle') or {}).get('textColor')} "
                    f"headerColor={(display_style_probe.get('displayStyle') or {}).get('headerColor')} "
                    f"fontPx={(display_style_probe.get('displayStyle') or {}).get('fontPx')} "
                    f"lineHeight={(display_style_probe.get('displayStyle') or {}).get('lineHeight')} "
                    f"displayTimerRaw={((display_style_probe.get('displayStyle') or {}).get('displayTimerState') or {}).get('rawOperandWord4')} "
                    f"branchFontRow={display_style_probe.get('branchFontRowEventCount')}:"
                    f"{((display_style_probe.get('branchFontRowFirst') or {}).get('vaHex'))}:"
                    f"{((display_style_probe.get('branchFontRowFirst') or {}).get('fontRow'))} "
                    f"branchDisplayStyle={display_style_probe.get('branchDisplayStyleEventCount')}:"
                    f"{((display_style_probe.get('branchDisplayStyleFirst') or {}).get('vaHex'))}:"
                    f"{((display_style_probe.get('branchDisplayStyleFirst') or {}).get('tableIndexByte'))}:"
                    f"{(((display_style_probe.get('branchDisplayStyleFirst') or {}).get('displayStyleRender') or {}).get('styleName'))} "
                    f"branchObjectPosition={display_style_probe.get('branchObjectPositionEventCount')}:"
                    f"{((display_style_probe.get('branchObjectPositionFirst') or {}).get('vaHex'))}:"
                    f"{((display_style_probe.get('branchObjectPositionFirst') or {}).get('x'))},"
                    f"{((display_style_probe.get('branchObjectPositionFirst') or {}).get('y'))} "
                    f"branchDisplayTimer={display_style_probe.get('branchDisplayTimerEventCount')}:"
                    f"{((display_style_probe.get('branchDisplayTimerFirst') or {}).get('vaHex'))}:"
                    f"{((display_style_probe.get('branchDisplayTimerFirst') or {}).get('rawOperandWord4'))} "
                    "browserEventVmDisplayStyleImplemented=True "
                    "browserEventVmDisplayTimerStateImplemented=True "
                    "browserEventVmBranchDisplayTimerImplemented=True "
                    "originalEventVmRuntimeImplemented=False"
                ),
                "dialogueCursorRelativeLayout": (
                    "event-dialogue-block-028 "
                    f"cursorRelativeCount={cursor_relative_probe.get('groundedControlCursorRelativeCount')} "
                    f"lineLayoutCount={cursor_relative_probe.get('cursorRelativeLineLayoutCount')} "
                    f"currentLine={cursor_relative_probe.get('line')} "
                    f"cursorRaw={((cursor_relative_probe.get('cursorLayout') or {}).get('rawX'))},"
                    f"{((cursor_relative_probe.get('cursorLayout') or {}).get('rawY'))} "
                    f"cursorRelative={((cursor_relative_probe.get('cursorLayout') or {}).get('relativeX'))},"
                    f"{((cursor_relative_probe.get('cursorLayout') or {}).get('relativeY'))} "
                    f"branchCursorRelative={cursor_relative_probe.get('branchCursorRelativeEventCount')}:"
                    f"{((cursor_relative_probe.get('branchCursorRelativeCurrent') or {}).get('vaHex'))}:"
                    f"{((cursor_relative_probe.get('branchCursorRelativeCurrent') or {}).get('rawX'))},"
                    f"{((cursor_relative_probe.get('branchCursorRelativeCurrent') or {}).get('rawY'))} "
                    f"branchDisplayTimer={cursor_relative_probe.get('branchDisplayTimerEventCount')}:"
                    f"{((cursor_relative_probe.get('branchDisplayTimerFirst') or {}).get('vaHex'))}:"
                    f"{((cursor_relative_probe.get('branchDisplayTimerFirst') or {}).get('rawOperandWord4'))} "
                    f"branchStyle={cursor_relative_probe.get('branchDisplayStyleEventCount')}:"
                    f"{((cursor_relative_probe.get('branchDisplayStyleFirst') or {}).get('vaHex'))}:"
                    f"{((cursor_relative_probe.get('branchDisplayStyleFirst') or {}).get('tableIndexByte'))} "
                    f"branchObject={cursor_relative_probe.get('branchObjectPositionEventCount')}:"
                    f"{((cursor_relative_probe.get('branchObjectPositionFirst') or {}).get('vaHex'))}:"
                    f"{((cursor_relative_probe.get('branchObjectPositionFirst') or {}).get('x'))},"
                    f"{((cursor_relative_probe.get('branchObjectPositionFirst') or {}).get('y'))} "
                    f"branchDataBank={cursor_relative_probe.get('branchDataBankEventCount')}:"
                    f"{((cursor_relative_probe.get('branchDataBankFirst') or {}).get('vaHex'))}:"
                    f"{((cursor_relative_probe.get('branchDataBankFirst') or {}).get('dataBank'))} "
                    "browserEventVmCursorRelativeLayoutImplemented=True "
                    "browserEventVmBranchCursorRelativeImplemented=True "
                    "browserEventVmBranchDisplayTimerImplemented=True "
                    "originalEventVmRuntimeImplemented=False"
                ),
                "dialogueLiteralPayload": (
                    "event-dialogue-block-001 "
                    f"literalEvents={literal_payload_probe.get('literalTextEventCount')} "
                    f"literalSamples={','.join((literal_payload_probe.get('literalTextSamples') or [])[:3])} "
                    f"branchLiteralExec={literal_payload_probe.get('branchLiteralTextPayloadExecutionCount')} "
                    f"branchLiteralMatched={literal_payload_probe.get('branchLiteralTextMatchedLineCount')} "
                    f"firstRender={((literal_payload_probe.get('firstRenderEvent') or {}).get('source'))}:"
                    f"{((literal_payload_probe.get('firstRenderEvent') or {}).get('lineSource'))}:"
                    f"{(((literal_payload_probe.get('firstRenderEvent') or {}).get('literalTextEvent') or {}).get('vaHex'))}:"
                    f"{((literal_payload_probe.get('firstRenderEvent') or {}).get('line'))} "
                    f"lineAdvance={((literal_payload_probe.get('lineAdvanceRenderEvent') or {}).get('source'))}:"
                    f"{((literal_payload_probe.get('lineAdvanceRenderEvent') or {}).get('lineSource'))}:"
                    f"{(((literal_payload_probe.get('lineAdvanceRenderEvent') or {}).get('literalTextEvent') or {}).get('vaHex'))}:"
                    f"{((literal_payload_probe.get('lineAdvanceRenderEvent') or {}).get('line'))} "
                    "browserEventVmLiteralTextPayloadLineImplemented=True "
                    "originalEventVmRuntimeImplemented=False"
                ),
                "dialogueObjectFocus": (
                    "event-dialogue-block-005 "
                    f"active={dialogue_object_focus.get('active')} "
                    f"rendered={dialogue_object_focus_render.get('rendered')} "
                    f"xy={dialogue_object_focus.get('x')},{dialogue_object_focus.get('y')} "
                    f"raw={dialogue_object_focus.get('rawX')},{dialogue_object_focus.get('rawY')} "
                    f"va={dialogue_object_focus.get('vaHex')} "
                    "browserEventVmObjectPositionFocusImplemented=True "
                    "originalObjectVmRuntimeImplemented=False"
                ),
                "pointerDialogueAdvance": (
                    "event-dialogue-block-005 "
                    f"advanced={(completion_state.get('pointerAdvance') or {}).get('advanced')} "
                    f"releaseCleared={(completion_state.get('pointerAdvance') or {}).get('releaseCleared')} "
                    f"index={(((completion_state.get('pointerAdvance') or {}).get('before') or {}).get('index'))}->"
                    f"{(((completion_state.get('pointerAdvance') or {}).get('after') or {}).get('index'))} "
                    f"dialogueSoundCount={pointer_dialogue_sound_counts.get('dialogue')} "
                    f"advanceSound={pointer_dialogue_advance_last.get('source')}:"
                    f"{pointer_dialogue_advance_last.get('lineIndexBefore')}->"
                    f"{pointer_dialogue_advance_last.get('lineIndexAfter')} "
                    f"branchRender={pointer_after_render_event.get('source')}:"
                    f"{pointer_after_render_event.get('vaHex')}:"
                    f"{pointer_after_render_event.get('branchExecutionStepIndex')}:"
                    f"{pointer_after_render_event.get('branchExecutionStackDepth')} "
                    f"branchRenderLine={pointer_after_render_event.get('line')} "
                    f"branchRenderSelection={pointer_after_render_event.get('browserEventVmBranchRenderSelectionImplemented')} "
                    f"branchLineAdvance={branch_line_advance_render_event.get('source')}:"
                    f"{branch_line_advance_render_event.get('lineIndex')}:"
                    f"{branch_line_advance_render_event.get('vaHex')}:"
                    f"{branch_line_advance_render_event.get('separatorOrdinal')} "
                    f"branchLineAdvanceLine={branch_line_advance_render_event.get('line')} "
                    f"branchLineAdvanceSelection={branch_line_advance_render_event.get('browserEventVmBranchLineAdvanceSelectionImplemented')} "
                    f"branchWait={branch_wait_latch_event.get('vaHex')}:"
                    f"{branch_wait_latch_event.get('lineIndex')}:"
                    f"{branch_wait_latch_event.get('waitOrdinal')} "
                    f"branchWaitLine={branch_wait_latch_event.get('line')} "
                    f"branchWaitLatch={branch_wait_latch.get('browserDialogueInputWaitBranchLineImplemented')} "
                    f"branchWaitChrome={branch_wait_chrome_event.get('browserEventVmBranchInputWaitImplemented')} "
                    "canvasPointer=True"
                ),
                "keyboardInputWaitLatch": (
                    "event-dialogue-block-005 "
                    f"firstAdvanced={(completion_state.get('keyboardInputWaitLatch') or {}).get('firstAdvanced')} "
                    f"repeatBlocked={(completion_state.get('keyboardInputWaitLatch') or {}).get('repeatBlocked')} "
                    f"releaseCleared={(completion_state.get('keyboardInputWaitLatch') or {}).get('releaseCleared')} "
                    f"secondAdvancedAfterRelease={(completion_state.get('keyboardInputWaitLatch') or {}).get('secondAdvancedAfterRelease')} "
                    f"index={((((completion_state.get('keyboardInputWaitLatch') or {}).get('before') or {}).get('index')))}->"
                    f"{((((completion_state.get('keyboardInputWaitLatch') or {}).get('afterFirst') or {}).get('index')))}->"
                    f"{((((completion_state.get('keyboardInputWaitLatch') or {}).get('afterRepeat') or {}).get('index')))}->"
                    f"{((((completion_state.get('keyboardInputWaitLatch') or {}).get('afterSecondAdvance') or {}).get('index')))} "
                    f"soundFirst={keyboard_dialogue_sound_first_counts.get('dialogue')} "
                    f"soundRepeat={keyboard_dialogue_sound_repeat_counts.get('dialogue')} "
                    f"soundSecond={keyboard_dialogue_sound_second_counts.get('dialogue')} "
                    "browserDialogueInputWaitLatchImplemented=True "
                    "originalEventVmRuntimeImplemented=False"
                ),
                "restoredDialogue": (
                    "event-dialogue-block-005 "
                    f"button={restore_state.get('buttonText')} "
                    f"next={restore_state.get('nextCandidateId')} "
                    f"progressRestored={restore_state.get('loaded')} "
                    f"dialogueCompleteCount={(restore_state.get('progress') or {}).get('counts', {}).get('dialogue-complete')} "
                    f"{story_flags_summary(restore_state)} "
                    "originalStoryFlagRuntimeImplemented=False"
                ),
                "allCompletedDialogue": (
                    "event-dialogue-block-005,event-dialogue-block-026 "
                    f"button={all_completion_state.get('buttonText')} "
                    f"notice={(all_completion_state.get('completionNotice') or {}).get('blockId')} "
                    f"completedActionResult={all_completion_state.get('completedActionResult')} "
                    f"objective={(all_completion_state.get('objectiveBefore') or {}).get('title')} "
                    f"objectiveNext={(all_completion_state.get('objectiveBefore') or {}).get('nextAction')} "
                    f"objectiveAction={(all_completion_state.get('objectiveAction') or {}).get('action')} "
                    f"objectiveActiveId={(all_completion_state.get('objectiveAction') or {}).get('activeId')} "
                    f"{dialogue_completion_notice_feedback_summary(all_completion_state)} "
                    f"menu={all_completion_state.get('menuDialogueName')} "
                    f"directMenuIndex={all_completion_state.get('menuDirectDialogueIndex')} "
                    f"menuMode={all_completion_state.get('menuModeAfterOpen')} "
                    f"menuCandidateCount={all_completion_state.get('menuCandidateCount')} "
                    f"dialogueCompleteCount={(all_completion_state.get('progress') or {}).get('counts', {}).get('dialogue-complete')} "
                    f"{story_flags_summary(all_completion_state)} "
                    f"autoSaved={(all_completion_state.get('autoSave') or {}).get('saved')} "
                    f"autoSource={(all_completion_state.get('autoSave') or {}).get('source')} "
                    "originalStoryFlagRuntimeImplemented=False"
                ),
                "titleRestoredDialogue": (
                    f"titleContinue={title_continue_restore.get('titleContinue')} "
                    f"label={title_continue_label} "
                    f"map={title_continue_restore.get('map')}@{(title_continue_restore.get('foot') or {}).get('x')},{(title_continue_restore.get('foot') or {}).get('y')} "
                    f"quickLoadText={title_continue_map.get('quickLoadText')} "
                    f"button={title_continue_restore.get('dialogueButtonText')} "
                    f"dialogueButtonResult={title_continue_restore.get('dialogueButtonResult')} "
                    f"objective={(title_continue_restore.get('objectiveBefore') or {}).get('title')} "
                    f"objectiveNext={(title_continue_restore.get('objectiveBefore') or {}).get('nextAction')} "
                    f"objectiveAction={(title_continue_restore.get('objectiveAction') or {}).get('action')} "
                    f"objectiveActiveId={(title_continue_restore.get('objectiveAction') or {}).get('activeId')} "
                    f"{dialogue_completion_notice_feedback_summary(title_continue_restore)} "
                    f"menu={title_continue_restore.get('menuDialogueName')} "
                    f"directMenuIndex={title_continue_restore.get('menuDirectDialogueIndex')} "
                    f"menuMode={title_continue_restore.get('menuModeAfterOpen')} "
                    f"menuCandidateCount={title_continue_restore.get('menuCandidateCount')} "
                    f"dialogueCompleteCount={(title_continue_restore.get('progress') or {}).get('counts', {}).get('dialogue-complete')} "
                    f"{story_flags_summary(title_continue_restore)} "
                    "originalStoryFlagRuntimeImplemented=False"
                ),
	                "snapshots": {
	                    "completion": completion_state,
                    "dialogueCandidateMenu": dialogue_menu_state,
	                    "dialoguePromptPriority": {
	                        "beforePrompt": completion_state.get("beforePrompt"),
	                        "promptActivation": completion_state.get("promptActivation"),
	                    },
	                    "mobileDialogueCancel": completion_state.get("mobileCancel"),
		                    "dialogueChrome": (mobile_cancel_before.get("dialogueChrome") or {}),
		                    "dialogueControlFlow": dialogue_branch_flow,
		                    "dialogueBranchExecution": dialogue_branch_execution,
		                    "dialogueRenderState": dialogue_render_event,
	                    "dialogueDisplayStyle": dialogue_display_style,
	                    "dialogueDisplayStyleProbe": display_style_probe,
	                    "dialogueCursorRelativeLayout": cursor_relative_probe,
                    "dialogueLiteralPayload": literal_payload_probe,
	                    "dialogueObjectFocus": {
	                        "focus": dialogue_object_focus,
	                        "render": dialogue_object_focus_render,
	                    },
	                    "pointerDialogueAdvance": completion_state.get("pointerAdvance"),
	                    "keyboardInputWaitLatch": completion_state.get("keyboardInputWaitLatch"),
                    "restore": restore_state,
                    "allCompletion": all_completion_state,
                    "continueTitle": continue_title,
                    "titleContinueClick": title_continue_click,
                    "continuedDialogueMap": continued_dialogue_map,
                    "titleContinueRestore": title_continue_restore,
                },
            }
            write_report(report)
            print(
                "ok candidate dialogue progress browser "
                f"dialogueMenu={report['dialogueCandidateMenu']} "
	                f"promptPriority={report['dialoguePromptPriority']} "
	                f"completed={report['completedDialogue']} mobileCancel={report['mobileDialogueCancel']} "
	                f"dialogueChrome={report['dialogueChrome']} "
	                f"dialogueControlFlow={report['dialogueControlFlow']} "
	                f"dialogueRenderState={report['dialogueRenderState']} "
	                f"dialogueDisplayStyle={report['dialogueDisplayStyle']} "
	                f"dialogueDisplayStyleProbe={report['dialogueDisplayStyleProbe']} "
	                f"dialogueCursorRelativeLayout={report['dialogueCursorRelativeLayout']} "
	                f"dialogueLiteralPayload={report['dialogueLiteralPayload']} "
	                f"restored={report['restoredDialogue']} "
                f"allCompleted={report['allCompletedDialogue']} "
                f"titleRestored={report['titleRestoredDialogue']}"
            )
        finally:
            if session_id:
                try:
                    request_json(port, "DELETE", f"/session/{session_id}", timeout=5)
                except Exception:
                    pass
            proc.terminate()
            try:
                proc.wait(timeout=5)
            except subprocess.TimeoutExpired:
                proc.kill()
                proc.wait(timeout=5)


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--base", default="http://127.0.0.1:8013")
    args = parser.parse_args()
    verify_browser(args.base)


if __name__ == "__main__":
    main()
