#!/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 parse_qs, 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]
TRIAL_BLOCK_REASONS = [
    "no map1_01a->map2_02d transition-review row",
    "no strict map1_01a event transition",
    "coordinate refs are non-promotable",
    "variant coordinate scan found no strict source coordinate",
    "tile signature matches only as geometry/tile evidence",
]
TRIAL_BLOCKER_SHORT = "trial-only: no map1_01a->map2_02d transition-review row +4"
TRIAL_BLOCKER_FULL = "; ".join(TRIAL_BLOCK_REASONS)


def search_param(search: str, key: str) -> str:
    parsed = parse_qs(str(search or "").lstrip("?"), keep_blank_values=True)
    values = parsed.get(key) or []
    return values[-1] if values else ""


def has_restored_route_query(search: str, map_name: str, start_tile: str) -> bool:
    return (
        search_param(search, "map") == map_name
        and search_param(search, "startTile") == start_tile
        and search_param(search, "trialTransitions") == "routeAssist"
    )


def expected_route_trial_block_reasons(source: str, target: str) -> list[str] | None:
    if source == "map1_01a" and target == "map2_02d":
        return TRIAL_BLOCK_REASONS
    return None


def verify_trial_blocker_fields(entry: dict, expected_reasons: list[str], context: str) -> None:
    if (
        entry.get("blockReasons") != expected_reasons
        or entry.get("blockReasonCount") != len(expected_reasons)
        or entry.get("stepBlockerText") != f"trial-only: {TRIAL_BLOCKER_FULL}"
        or entry.get("routeBlockerShortText") != TRIAL_BLOCKER_SHORT
        or entry.get("strictHotspotStatus") != "blocked"
        or entry.get("promotionBlockSummary") != TRIAL_BLOCKER_FULL
        or entry.get("routePromotionStatus") != "trial-only"
    ):
        raise WebDriverError(f"{context} missing trial blocker fields: {entry!r}")


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 verify_route_gate_feedback(
    state: dict,
    expected_source: str,
    expected_gate_source: str,
    expected_text: str,
    expected_route_clear_count: int,
) -> None:
    feedback_log = state.get("routeGateFeedbackLog") or []
    feedback_render_log = state.get("routeGateFeedbackRender") or []
    feedback = state.get("routeGateFeedbackLast") or {}
    feedback_render = state.get("routeGateFeedbackLastRender") or {}
    if (
        len(feedback_log) < 1
        or len(feedback_render_log) < 1
        or feedback.get("source") != expected_source
        or feedback.get("gateSource") != expected_gate_source
        or feedback.get("text") != expected_text
        or feedback.get("map") != "map2_14j"
        or feedback.get("currentMap") != "map2_14j"
        or feedback.get("selectedRouteGoal") != "map2_14j"
        or feedback.get("routeCandidateCount") != 4
        or feedback.get("fieldEncounterVictoryCount") != 1
        or feedback.get("routeCompleteCount") != 1
        or feedback.get("routeClearCount") != expected_route_clear_count
        or feedback.get("routeGateSound") != "victory"
        or not str(feedback.get("routeGateSoundSrc") or "").endswith("/extract_wlk/12.wav")
        or feedback.get("routeGateSoundPlayed") is not True
        or feedback.get("playableGateSource") != "prototype-playable-route-gate"
        or feedback.get("playableGateStatus") != "open"
        or feedback.get("playableGateOpen") is not True
        or feedback.get("durationMs") != 1300
        or feedback.get("browserRouteGateFeedbackImplemented") is not True
        or feedback.get("prototypeRouteAssistCompletionImplemented") is not True
        or feedback.get("originalFullGameCompletionImplemented") is not False
        or feedback.get("originalRoutePromotionImplemented") is not False
        or feedback.get("originalStoryFlagRuntimeImplemented") is not False
        or feedback_render.get("active") is not True
        or feedback_render.get("source") != expected_source
        or feedback_render.get("gateSource") != expected_gate_source
        or feedback_render.get("text") != expected_text
        or feedback_render.get("map") != "map2_14j"
        or feedback_render.get("currentMap") != "map2_14j"
        or feedback_render.get("selectedRouteGoal") != "map2_14j"
        or feedback_render.get("routeCandidateCount") != 4
        or feedback_render.get("fieldEncounterVictoryCount") != 1
        or feedback_render.get("routeCompleteCount") != 1
        or feedback_render.get("routeClearCount") != expected_route_clear_count
        or feedback_render.get("routeGateSound") != "victory"
        or not str(feedback_render.get("routeGateSoundSrc") or "").endswith("/extract_wlk/12.wav")
        or feedback_render.get("routeGateSoundPlayed") is not True
        or feedback_render.get("playableGateSource") != "prototype-playable-route-gate"
        or feedback_render.get("playableGateStatus") != "open"
        or feedback_render.get("playableGateOpen") is not True
        or feedback_render.get("durationMs") != 1300
        or feedback_render.get("browserRouteGateFeedbackImplemented") is not True
        or feedback_render.get("prototypeRouteAssistCompletionImplemented") is not True
        or feedback_render.get("originalFullGameCompletionImplemented") is not False
        or feedback_render.get("originalRoutePromotionImplemented") is not False
        or feedback_render.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"route gate feedback is incomplete: {state!r}")


def route_progress_sound_summary(state: dict) -> str:
    feedback = state.get("routeProgressFeedbackLast") or {}
    return (
        f"routeProgressSound={feedback.get('routeProgressSound') or ''} "
        f"routeProgressSoundSrc={feedback.get('routeProgressSoundSrc') or ''} "
        f"routeProgressSoundPlayed={feedback.get('routeProgressSoundPlayed')}"
    )


def story_flag_keys(flags: dict | None) -> set[str]:
    row = flags or {}
    keys = {
        str(record.get("key") or "")
        for record in row.get("records") or []
        if isinstance(record, dict)
    }
    keys.update(str(key) for key in (row.get("flags") or {}).keys())
    return {key for key in keys if key}


def verify_story_flags(
    flags: dict | None,
    expected_keys: set[str],
    expected_counts: dict[str, int],
    context: str,
) -> None:
    row = flags or {}
    counts = row.get("counts") or {}
    if (
        row.get("source") != "prototype-progress-derived-story-flags"
        or row.get("browserPrototypeStoryFlagImplemented") is not True
        or row.get("originalStoryFlagRuntimeImplemented") is not False
        or not expected_keys.issubset(story_flag_keys(row))
        or any(counts.get(kind) != count for kind, count in expected_counts.items())
    ):
        raise WebDriverError(f"{context} story flags are incomplete: {flags!r}")


def story_flag_report(flags: dict | None, prefix: str = "story") -> str:
    row = flags or {}
    counts = row.get("counts") or {}
    keys = ",".join(sorted(story_flag_keys(row))) or "-"
    count_text = ",".join(f"{kind}:{counts[kind]}" for kind in sorted(counts)) or "-"
    return (
        f"{prefix}Flags={row.get('flagCount')} "
        f"{prefix}FlagCounts={count_text} "
        f"{prefix}FlagKeys={keys} "
        f"{prefix}FlagSource={row.get('source')}"
    )


def verify_route_progress_feedback(
    state: dict,
    expected_map: str,
    expected_source_map: str,
    expected_target_map: str,
    expected_text: str,
    expected_count: int,
    expected_progress_count: int,
    *,
    expected_trigger: str,
    expected_candidate_kind: str,
    expected_route_auto_save: bool,
    expected_direct_target_url: bool,
    expected_block_reasons: list[str] | None = None,
) -> None:
    feedback_log = state.get("routeProgressFeedbackLog") or []
    feedback_render_log = state.get("routeProgressFeedbackRender") or []
    feedback = state.get("routeProgressFeedbackLast") or {}
    feedback_render = state.get("routeProgressFeedbackLastRender") or {}
    if (
        len(feedback_log) < 1
        or len(feedback_render_log) < 1
        or feedback.get("source") != "route-progress-feedback"
        or feedback.get("text") != expected_text
        or feedback.get("map") != expected_map
        or feedback.get("currentMap") != expected_map
        or feedback.get("sourceMap") != expected_source_map
        or feedback.get("targetMap") != expected_target_map
        or feedback.get("routeCandidateCount") != expected_count
        or feedback.get("routeProgressCompletedCount") != expected_count
        or feedback.get("routeProgressCount") != expected_progress_count
        or feedback.get("progressEventKind") != "route-candidate"
        or feedback.get("progressEventId") != f"{expected_source_map}->{expected_target_map}"
        or feedback.get("routeProgressSound") != "menuConfirm"
        or not str(feedback.get("routeProgressSoundSrc") or "").endswith("/extract_wlk/04.wav")
        or feedback.get("routeProgressSoundPlayed") is not True
        or feedback.get("trigger") != expected_trigger
        or feedback.get("candidateKind") != expected_candidate_kind
        or feedback.get("trialTransitionMode") != "routeAssist"
        or feedback.get("routeAutoSave") is not expected_route_auto_save
        or feedback.get("directTargetUrl") is not expected_direct_target_url
        or feedback.get("autoSaved") is not True
        or feedback.get("durationMs") != 1200
        or feedback.get("browserRouteProgressFeedbackImplemented") is not True
        or feedback.get("prototypeRouteAssistProgressImplemented") is not True
        or feedback.get("originalRoutePromotionImplemented") is not False
        or feedback.get("originalStoryFlagRuntimeImplemented") is not False
        or feedback_render.get("active") is not True
        or feedback_render.get("source") != "route-progress-feedback"
        or feedback_render.get("text") != expected_text
        or feedback_render.get("map") != expected_map
        or feedback_render.get("currentMap") != expected_map
        or feedback_render.get("sourceMap") != expected_source_map
        or feedback_render.get("targetMap") != expected_target_map
        or feedback_render.get("routeCandidateCount") != expected_count
        or feedback_render.get("routeProgressCompletedCount") != expected_count
        or feedback_render.get("routeProgressCount") != expected_progress_count
        or feedback_render.get("progressEventKind") != "route-candidate"
        or feedback_render.get("progressEventId") != f"{expected_source_map}->{expected_target_map}"
        or feedback_render.get("routeProgressSound") != "menuConfirm"
        or not str(feedback_render.get("routeProgressSoundSrc") or "").endswith("/extract_wlk/04.wav")
        or feedback_render.get("routeProgressSoundPlayed") is not True
        or feedback_render.get("trigger") != expected_trigger
        or feedback_render.get("candidateKind") != expected_candidate_kind
        or feedback_render.get("trialTransitionMode") != "routeAssist"
        or feedback_render.get("routeAutoSave") is not expected_route_auto_save
        or feedback_render.get("directTargetUrl") is not expected_direct_target_url
        or feedback_render.get("autoSaved") is not True
        or feedback_render.get("durationMs") != 1200
        or feedback_render.get("browserRouteProgressFeedbackImplemented") is not True
        or feedback_render.get("prototypeRouteAssistProgressImplemented") is not True
        or feedback_render.get("originalRoutePromotionImplemented") is not False
        or feedback_render.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"route progress feedback is incomplete: {state!r}")
    if expected_block_reasons is not None:
        verify_trial_blocker_fields(feedback, expected_block_reasons, "route progress feedback")
        verify_trial_blocker_fields(feedback_render, expected_block_reasons, "route progress feedback render")


def default_prototype_map_exit_script() -> str:
    return """
window.__hwanseDefaultPrototypeMapExit = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
activeDialogue = null;
menuOpen = false;
trialTransitions = '';
selectedTransitionTarget = 'map2_02d';
pendingTransitionTarget = 'map2_02d';
transitionTargetExplicit = true;
window.HWANSE_LAST_ROUTE_CANDIDATE_AUTO_SAVE = null;
window.HWANSE_LAST_PROTOTYPE_MAP_EXIT_TRANSITION = null;
window.HWANSE_ROUTE_PROGRESS_FEEDBACK_LOG = [];
window.HWANSE_ROUTE_PROGRESS_FEEDBACK_RENDER = [];
window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK = null;
window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK_RENDER = null;
if (typeof setupTransitionSelect === 'function') setupTransitionSelect();
if (typeof render === 'function') render();
const before = {
  scene,
  map: map?.name || '',
  tile: footTile(),
  search: window.location.search,
  trialTransitions,
  prototypeMapExitTransitions: typeof usesMapExitPrototypeTransitions === 'function'
    ? usesMapExitPrototypeTransitions()
    : null,
  mapExitGameplayTransitions: typeof usesMapExitGameplayTransitions === 'function'
    ? usesMapExitGameplayTransitions()
    : null,
  targets: transitionTargetsAtFoot(),
  prompt: window.HWANSE_LAST_ACTION_PROMPT || null,
  transitionSelectHidden: document.getElementById('transitionSelect')?.hidden ?? null,
};
const handled = checkMapExitTrialTransition(0, -1);
const deadline = performance.now() + 6000;
function finishWhenReady() {
  const autoSave = window.HWANSE_LAST_ROUTE_CANDIDATE_AUTO_SAVE || null;
  if (map?.name === 'map2_02d' && autoSave?.saved === true) {
    render();
    const raw = localStorage.getItem(RUNTIME_SAVE_KEY) || '{}';
    const payload = JSON.parse(raw);
    startPrototypeProgressReview();
    window.__hwanseDefaultPrototypeMapExit = {
      handled,
      saved: autoSave?.saved === true,
      autoSave,
      before,
      scene,
      map: map?.name || '',
      tile: footTile(),
      search: window.location.search,
      trialTransitions,
      prototypeMapExitTransition: window.HWANSE_LAST_PROTOTYPE_MAP_EXIT_TRANSITION || null,
      activeId: activeDialogue?.block?.blockId || '',
      activeLines: activeDialogue?.lines?.slice(0, 12) || [],
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
      savedProgress: payload.prototypeProgress || null,
      payloadMap: payload.map || '',
      payloadTile: payload.tile || null,
      savedTrialTransitions: payload.routeState?.trialTransitions || '',
      routeProgressFeedbackLog: window.HWANSE_ROUTE_PROGRESS_FEEDBACK_LOG || [],
      routeProgressFeedbackRender: window.HWANSE_ROUTE_PROGRESS_FEEDBACK_RENDER || [],
      routeProgressFeedbackLast: window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK || null,
      routeProgressFeedbackLastRender: window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK_RENDER || null,
    };
    return;
  }
  if (performance.now() >= deadline) {
    window.__hwanseDefaultPrototypeMapExit = {
      error: `default prototype map exit timed out at ${map?.name || ''}`,
      handled,
      before,
      map: map?.name || '',
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
    };
    return;
  }
  requestAnimationFrame(finishWhenReady);
}
finishWhenReady();
return true;
"""


def default_prototype_map_exit_state_script() -> str:
    return "return window.__hwanseDefaultPrototypeMapExit || null;"


def start_route_progress_script() -> str:
    return """
window.__hwanseCandidateRouteProgress = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
window.HWANSE_LAST_ROUTE_CANDIDATE_AUTO_SAVE = null;
window.HWANSE_ROUTE_PROGRESS_FEEDBACK_LOG = [];
window.HWANSE_ROUTE_PROGRESS_FEEDBACK_RENDER = [];
window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK = null;
window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK_RENDER = null;
selectedTransitionTarget = 'map2_02d';
pendingTransitionTarget = 'map2_02d';
transitionTargetExplicit = true;
const before = {
  scene,
  map: map?.name || '',
  tile: footTile(),
  targets: transitionTargetsAtFoot(),
};
const handled = checkMapExitTrialTransition(0, -1);
const deadline = performance.now() + 6000;
function finishWhenReady() {
  const autoSave = window.HWANSE_LAST_ROUTE_CANDIDATE_AUTO_SAVE || null;
  if (map?.name === 'map2_02d' && autoSave?.saved === true) {
    render();
    const raw = localStorage.getItem(RUNTIME_SAVE_KEY) || '{}';
    const payload = JSON.parse(raw);
    startPrototypeProgressReview();
    window.__hwanseCandidateRouteProgress = {
      handled,
      saved: autoSave?.saved === true,
      autoSave,
      before,
      scene,
      map: map?.name || '',
      tile: footTile(),
      search: window.location.search,
      trialTransitions,
      activeId: activeDialogue?.block?.blockId || '',
      activeLines: activeDialogue?.lines?.slice(0, 12) || [],
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
      savedProgress: payload.prototypeProgress || null,
      payloadMap: payload.map || '',
      payloadTile: payload.tile || null,
      labels: menuItems().map((item) => menuItemLabel(item)),
      routePathLabels: [...routePathSelect.options].map((option) => option.textContent || ''),
      routeNextText: routeNextButton.textContent || '',
      routeNextTitle: routeNextButton.title || '',
      routeProgressFeedbackLog: window.HWANSE_ROUTE_PROGRESS_FEEDBACK_LOG || [],
      routeProgressFeedbackRender: window.HWANSE_ROUTE_PROGRESS_FEEDBACK_RENDER || [],
      routeProgressFeedbackLast: window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK || null,
      routeProgressFeedbackLastRender: window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK_RENDER || null,
    };
    return;
  }
  if (performance.now() >= deadline) {
    window.__hwanseCandidateRouteProgress = {
      error: `route candidate transition timed out at ${map?.name || ''}`,
      handled,
      before,
      map: map?.name || '',
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
    };
    return;
  }
  requestAnimationFrame(finishWhenReady);
}
finishWhenReady();
return true;
"""


def restore_route_progress_script() -> str:
    return """
window.__hwanseCandidateRouteProgressRestore = null;
quickLoadRuntime()
  .then((loaded) => {
    startPrototypeProgressReview();
    window.__hwanseCandidateRouteProgressRestore = {
      loaded,
      scene,
      map: map?.name || '',
      tile: footTile(),
      search: window.location.search,
      trialTransitions,
      activeId: activeDialogue?.block?.blockId || '',
      activeLines: activeDialogue?.lines?.slice(0, 12) || [],
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      labels: menuItems().map((item) => menuItemLabel(item)),
      routePathLabels: [...routePathSelect.options].map((option) => option.textContent || ''),
      routeNextText: routeNextButton.textContent || '',
      routeNextTitle: routeNextButton.title || '',
    };
  })
  .catch((error) => {
    window.__hwanseCandidateRouteProgressRestore = { error: String(error && error.message || error) };
  });
return true;
"""


def route_progress_state_script() -> str:
    return "return window.__hwanseCandidateRouteProgress || null;"


def route_progress_restore_state_script() -> str:
    return "return window.__hwanseCandidateRouteProgressRestore || null;"


def route_menu_start_script() -> str:
    return """
window.__hwanseCandidateRouteMenuStart = null;
try {
  activeDialogue = null;
  menuOpen = true;
  menuMode = 'main';
  selectedMenuItemIndex = 0;
  const beforeItems = menuItems();
  const startIndex = beforeItems.findIndex((item) => item.command === 'startRouteAssistPath');
  selectedMenuItemIndex = startIndex >= 0 ? startIndex : 0;
  const used = useSelectedMenuItem();
  const afterItems = menuItems();
  const nextItem = afterItems.find((item) => item.command === 'activateNextRoutePath') || null;
  window.__hwanseCandidateRouteMenuStart = {
    used,
    scene,
    map: map?.name || '',
    tile: footTile(),
    search: window.location.search,
    trialTransitions,
    selectedRouteGoal,
    beforeLabels: beforeItems.map((item) => menuItemLabel(item)),
    afterLabels: afterItems.map((item) => menuItemLabel(item)),
    startIndex,
    startItem: beforeItems[startIndex] || null,
    nextItem,
    routePathValue: routePathSelect.value || '',
    routeNextText: routeNextButton.textContent || '',
    routeNextTitle: routeNextButton.title || '',
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
    menuNotice,
  };
} catch (error) {
  window.__hwanseCandidateRouteMenuStart = { error: String(error && error.message || error) };
}
return true;
"""


def route_menu_start_state_script() -> str:
    return "return window.__hwanseCandidateRouteMenuStart || null;"


def route_goal_menu_script() -> str:
    return """
window.__hwanseCandidateRouteGoalMenu = null;
try {
  activeDialogue = null;
  menuOpen = true;
  menuMode = 'main';
  selectedMenuItemIndex = 0;
  const mainItems = menuItems();
  const goalMenuIndex = mainItems.findIndex((item) => item.command === 'openRouteGoalMenu');
  selectedMenuItemIndex = goalMenuIndex >= 0 ? goalMenuIndex : 0;
  const opened = useSelectedMenuItem();
  const goalItems = menuItems();
  const targetIndex = goalItems.findIndex((item) => item.command === 'selectRouteAssistGoal' && item.routeTarget === 'map2_18d');
  selectedMenuItemIndex = targetIndex >= 0 ? targetIndex : 0;
  const targetItem = goalItems[selectedMenuItemIndex] || null;
  const selected = useSelectedMenuItem();
  const afterItems = menuItems();
  const nextItem = afterItems.find((item) => item.command === 'activateNextRoutePath') || null;
  window.__hwanseCandidateRouteGoalMenu = {
    opened,
    selected,
    scene,
    map: map?.name || '',
    tile: footTile(),
    search: window.location.search,
    trialTransitions,
    selectedRouteGoal,
    menuMode,
    menuNotice,
    goalMenuIndex,
    targetIndex,
    goalMenuItem: mainItems[goalMenuIndex] || null,
    targetItem,
    nextItem,
    mainLabels: mainItems.map((item) => menuItemLabel(item)),
    goalLabels: goalItems.map((item) => menuItemLabel(item)),
    afterLabels: afterItems.map((item) => menuItemLabel(item)),
    routePathValue: routePathSelect.value || '',
    routeNextText: routeNextButton.textContent || '',
    routeNextTitle: routeNextButton.title || '',
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  };
} catch (error) {
  window.__hwanseCandidateRouteGoalMenu = { error: String(error && error.message || error) };
}
return true;
"""


def route_goal_menu_state_script() -> str:
    return "return window.__hwanseCandidateRouteGoalMenu || null;"


def route_objective_action_script(storage_key: str) -> str:
    return f"""
const storageKey = "{storage_key}";
try {{
  activeDialogue = null;
  menuOpen = false;
  if (typeof setupRoutePathSelect === 'function') setupRoutePathSelect();
  if (typeof render === 'function') render();
  const beforeItems = typeof menuItems === 'function' ? menuItems() : [];
  const beforeRecord = {{
    beforeMap: map?.name || '',
    beforeTile: footTile(),
    beforeSearch: window.location.search,
    beforeTrialTransitions: trialTransitions,
    beforeSelectedRouteGoal: selectedRouteGoal,
    beforeRoutePathValue: routePathSelect.value || '',
    beforeRouteNextText: routeNextButton.textContent || '',
    beforeRouteNextTitle: routeNextButton.title || '',
    beforeRouteContinuationTarget: routeNextButton.dataset.routeContinuationTarget || '',
    beforeObjective: typeof prototypeObjectiveState === 'function' ? prototypeObjectiveState() : null,
    beforeLabels: beforeItems.map((item) => menuItemLabel(item)),
    beforeHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  }};
  const objectiveActionResult = typeof activatePrototypeObjectiveAction === 'function'
    ? activatePrototypeObjectiveAction()
    : false;
  const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
  sessionStorage.setItem(storageKey, JSON.stringify({{
    ...beforeRecord,
    objectiveActionResult,
    objectiveAction,
  }}));
}} catch (error) {{
  sessionStorage.setItem(storageKey, JSON.stringify({{ error: String(error && error.message || error) }}));
}}
return true;
"""


def route_objective_action_state_script(storage_key: str) -> str:
    return f"""
const storageKey = "{storage_key}";
let record = {{}};
try {{
  record = JSON.parse(sessionStorage.getItem(storageKey) || "{{}}");
}} catch (error) {{
  record = {{ error: String(error && error.message || error) }};
}}
let payload = null;
try {{
  payload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
}} catch (error) {{
  payload = null;
}}
try {{
  if (typeof render === 'function') render();
  const currentItems = typeof menuItems === 'function' ? menuItems() : [];
  const params = new URLSearchParams(window.location.search);
  return {{
    ...record,
    readyState: document.readyState,
    scene: typeof scene === 'undefined' ? '' : scene,
    map: typeof map === 'undefined' || !map ? '' : map.name,
    tile: typeof footTile === 'function' ? footTile() : null,
    search: window.location.search,
    transitionTargetParam: params.get('transitionTarget') || '',
    routeGoalParam: params.get('routeGoal') || '',
    routeAutoSaveParam: params.get('routeAutoSave') || '',
    routeStepSourceParam: params.get('routeStepSource') || '',
    routeStepTargetParam: params.get('routeStepTarget') || '',
    routeStepKindParam: params.get('routeStepKind') || '',
    trialTransitions: typeof trialTransitions === 'undefined' ? '' : trialTransitions,
    selectedRouteGoal: typeof selectedRouteGoal === 'undefined' ? '' : selectedRouteGoal,
    selectedTransitionTarget: typeof selectedTransitionTarget === 'undefined' ? '' : selectedTransitionTarget,
    routePathValue: document.getElementById('routePathSelect')?.value || '',
    routePathLabels: [...(document.getElementById('routePathSelect')?.options || [])].map((option) => option.textContent || ''),
    routeNextText: document.getElementById('routeNextButton')?.textContent || '',
    routeNextTitle: document.getElementById('routeNextButton')?.title || '',
    routeContinuationTarget: document.getElementById('routeNextButton')?.dataset.routeContinuationTarget || '',
    fieldEncounter: typeof fieldEncounterSavePayload === 'function' ? fieldEncounterSavePayload() : null,
    fieldEncounterMenuLabel: typeof fieldEncounterMenuLabel === 'function' ? fieldEncounterMenuLabel() : '',
    fieldEncounterMode: window.HWANSE_LAST_FIELD_ENCOUNTER_MODE || null,
    fieldEncounterModeAutoSave: window.HWANSE_LAST_FIELD_ENCOUNTER_MODE_AUTO_SAVE || null,
    savedPayloadMap: payload?.map || '',
    savedFieldEncounter: payload?.fieldEncounter || null,
    savedProgress: payload?.prototypeProgress || null,
    labels: currentItems.map((item) => menuItemLabel(item)),
    currentCommands: currentItems.map((item) => ({{
      name: menuItemLabel(item),
      command: item.command || '',
      usable: item.usable !== false,
      routeTarget: item.routeTarget || '',
      routeNextTarget: item.routeNextTarget || '',
    }})),
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []),
  }};
}} catch (error) {{
  return {{ ...record, error: String(error && error.message || error) }};
}}
"""


def quick_objective_sound_reset_script() -> str:
    return """
  window.HWANSE_SOUND_LOG = [];
  window.HWANSE_SOUND_COUNTS = {};
  window.HWANSE_LAST_SOUND = null;
  window.HWANSE_QUICK_OBJECTIVE_SOUND_LOG = [];
  window.HWANSE_LAST_QUICK_OBJECTIVE_SOUND = null;
  sessionStorage.removeItem('HWANSE_LAST_QUICK_OBJECTIVE_SOUND');
"""


def route_control_sound_reset_script() -> str:
    return """
  window.HWANSE_SOUND_LOG = [];
  window.HWANSE_SOUND_COUNTS = {};
  window.HWANSE_LAST_SOUND = null;
  window.HWANSE_ROUTE_CONTROL_SOUND_LOG = [];
  window.HWANSE_LAST_ROUTE_CONTROL_SOUND = null;
  sessionStorage.removeItem('HWANSE_LAST_ROUTE_CONTROL_ACTION');
  sessionStorage.removeItem('HWANSE_LAST_ROUTE_CONTROL_SOUND');
"""


def quick_objective_keyboard_script(storage_key: str) -> str:
    return f"""
const storageKey = "{storage_key}";
try {{
  activeDialogue = null;
  menuOpen = false;
  if (typeof setupRoutePathSelect === 'function') setupRoutePathSelect();
  if (typeof render === 'function') render();
  sessionStorage.removeItem('HWANSE_LAST_QUICK_OBJECTIVE_ACTION');
{quick_objective_sound_reset_script()}
  const button = document.getElementById('virtualObjectiveButton');
  const beforeRecord = {{
    beforeMap: map?.name || '',
    beforeTile: footTile(),
    beforeSearch: window.location.search,
    beforeTrialTransitions: trialTransitions,
    beforeSelectedRouteGoal: selectedRouteGoal,
    beforeRoutePathValue: routePathSelect.value || '',
    beforeButtonHidden: button?.hidden ?? null,
    beforeButtonTitle: button?.title || '',
    beforeHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  }};
  const event = new KeyboardEvent('keydown', {{
    code: 'KeyG',
    key: 'g',
    bubbles: true,
    cancelable: true,
  }});
  const dispatched = window.dispatchEvent(event);
  sessionStorage.setItem(storageKey, JSON.stringify({{
    ...beforeRecord,
    dispatched,
    defaultPrevented: event.defaultPrevented,
  }}));
}} catch (error) {{
  sessionStorage.setItem(storageKey, JSON.stringify({{ error: String(error && error.message || error) }}));
}}
return true;
"""


def quick_objective_button_script(storage_key: str) -> str:
    return f"""
const storageKey = "{storage_key}";
try {{
  activeDialogue = null;
  menuOpen = false;
  if (typeof setupRoutePathSelect === 'function') setupRoutePathSelect();
  if (typeof render === 'function') render();
  sessionStorage.removeItem('HWANSE_LAST_QUICK_OBJECTIVE_ACTION');
{quick_objective_sound_reset_script()}
  const button = document.getElementById('virtualObjectiveButton');
  const beforeRecord = {{
    beforeMap: map?.name || '',
    beforeTile: footTile(),
    beforeSearch: window.location.search,
    beforeTrialTransitions: trialTransitions,
    beforeSelectedRouteGoal: selectedRouteGoal,
    beforeRoutePathValue: routePathSelect.value || '',
    beforeButtonHidden: button?.hidden ?? null,
    beforeButtonTitle: button?.title || '',
    beforeHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  }};
  if (!button) {{
    sessionStorage.setItem(storageKey, JSON.stringify({{ ...beforeRecord, error: 'missing virtualObjectiveButton' }}));
    return true;
  }}
  button.dispatchEvent(new PointerEvent('pointerdown', {{
    pointerId: 91,
    pointerType: 'touch',
    bubbles: true,
    cancelable: true,
  }}));
  button.dispatchEvent(new PointerEvent('pointerup', {{
    pointerId: 91,
    pointerType: 'touch',
    bubbles: true,
    cancelable: true,
  }}));
  sessionStorage.setItem(storageKey, JSON.stringify({{
    ...beforeRecord,
    clicked: true,
  }}));
}} catch (error) {{
  sessionStorage.setItem(storageKey, JSON.stringify({{ error: String(error && error.message || error) }}));
}}
return true;
"""


def quick_objective_toolbar_script(storage_key: str) -> str:
    return f"""
const storageKey = "{storage_key}";
try {{
  activeDialogue = null;
  menuOpen = false;
  if (typeof setupRoutePathSelect === 'function') setupRoutePathSelect();
  if (typeof render === 'function') render();
  sessionStorage.removeItem('HWANSE_LAST_QUICK_OBJECTIVE_ACTION');
{quick_objective_sound_reset_script()}
  const button = document.getElementById('objectiveActionButton');
  const beforeRecord = {{
    beforeMap: map?.name || '',
    beforeTile: footTile(),
    beforeSearch: window.location.search,
    beforeTrialTransitions: trialTransitions,
    beforeSelectedRouteGoal: selectedRouteGoal,
    beforeRoutePathValue: routePathSelect.value || '',
    beforeObjectiveButtonHidden: button?.hidden ?? null,
    beforeObjectiveButtonDisabled: button?.disabled ?? null,
    beforeObjectiveButtonText: button?.textContent || '',
    beforeObjectiveButtonTitle: button?.title || '',
    beforeHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  }};
  if (!button) {{
    sessionStorage.setItem(storageKey, JSON.stringify({{ ...beforeRecord, error: 'missing objectiveActionButton' }}));
    return true;
  }}
  const event = new MouseEvent('click', {{
    bubbles: true,
    cancelable: true,
    detail: 1,
  }});
  const dispatched = button.dispatchEvent(event);
  sessionStorage.setItem(storageKey, JSON.stringify({{
    ...beforeRecord,
    clicked: true,
    dispatched,
    defaultPrevented: event.defaultPrevented,
  }}));
}} catch (error) {{
  sessionStorage.setItem(storageKey, JSON.stringify({{ error: String(error && error.message || error) }}));
}}
return true;
"""


def quick_objective_control_state_script(storage_key: str) -> str:
    return f"""
const storageKey = "{storage_key}";
let record = {{}};
try {{
  record = JSON.parse(sessionStorage.getItem(storageKey) || "{{}}");
}} catch (error) {{
  record = {{ error: String(error && error.message || error) }};
}}
let quickAction = null;
try {{
  quickAction = JSON.parse(sessionStorage.getItem('HWANSE_LAST_QUICK_OBJECTIVE_ACTION') || "null");
}} catch (error) {{
  quickAction = null;
}}
let quickSound = null;
try {{
  quickSound = JSON.parse(sessionStorage.getItem('HWANSE_LAST_QUICK_OBJECTIVE_SOUND') || "null");
}} catch (error) {{
  quickSound = null;
}}
let payload = null;
try {{
  payload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
}} catch (error) {{
  payload = null;
}}
try {{
  if (typeof render === 'function') render();
  const button = document.getElementById('virtualObjectiveButton');
  const toolbarButton = document.getElementById('objectiveActionButton');
  const params = new URLSearchParams(window.location.search);
  return {{
    ...record,
    quickAction,
    quickSound,
    readyState: document.readyState,
    scene: typeof scene === 'undefined' ? '' : scene,
    map: typeof map === 'undefined' || !map ? '' : map.name,
    tile: typeof footTile === 'function' ? footTile() : null,
    search: window.location.search,
    routeGoalParam: params.get('routeGoal') || '',
    transitionTargetParam: params.get('transitionTarget') || '',
    routeAutoSaveParam: params.get('routeAutoSave') || '',
    trialTransitions: typeof trialTransitions === 'undefined' ? '' : trialTransitions,
    selectedRouteGoal: typeof selectedRouteGoal === 'undefined' ? '' : selectedRouteGoal,
    routePathValue: document.getElementById('routePathSelect')?.value || '',
    routeNextText: document.getElementById('routeNextButton')?.textContent || '',
    routeNextTitle: document.getElementById('routeNextButton')?.title || '',
    fieldEncounter: typeof fieldEncounterSavePayload === 'function' ? fieldEncounterSavePayload() : null,
    fieldEncounterMode: window.HWANSE_LAST_FIELD_ENCOUNTER_MODE || null,
    fieldEncounterModeAutoSave: window.HWANSE_LAST_FIELD_ENCOUNTER_MODE_AUTO_SAVE || null,
    buttonHidden: button?.hidden ?? null,
    buttonTitle: button?.title || '',
    objectiveButtonHidden: toolbarButton?.hidden ?? null,
    objectiveButtonDisabled: toolbarButton?.disabled ?? null,
    objectiveButtonText: toolbarButton?.textContent || '',
    objectiveButtonTitle: toolbarButton?.title || '',
    savedPayloadMap: payload?.map || '',
    savedFieldEncounter: payload?.fieldEncounter || null,
    quickObjectiveSoundLog: Array.isArray(window.HWANSE_QUICK_OBJECTIVE_SOUND_LOG) ? window.HWANSE_QUICK_OBJECTIVE_SOUND_LOG.slice() : [],
    quickObjectiveSoundLogLength: Array.isArray(window.HWANSE_QUICK_OBJECTIVE_SOUND_LOG) ? window.HWANSE_QUICK_OBJECTIVE_SOUND_LOG.length : 0,
    lastQuickObjectiveSound: window.HWANSE_LAST_QUICK_OBJECTIVE_SOUND || quickSound,
    soundCounts: {{ ...(window.HWANSE_SOUND_COUNTS || {{}}) }},
    menuConfirmSoundCount: Number((window.HWANSE_SOUND_COUNTS || {{}}).menuConfirm || 0),
    lastSound: window.HWANSE_LAST_SOUND || null,
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []),
  }};
}} catch (error) {{
  return {{ ...record, quickAction, quickSound, error: String(error && error.message || error) }};
}}
"""


def route_continuation_menu_script() -> str:
    return """
const storageKey = "__hwanseCandidateRouteContinuationMenu";
try {
  activeDialogue = null;
  menuOpen = true;
  menuMode = 'main';
  selectedMenuItemIndex = 0;
  if (typeof setupRoutePathSelect === 'function') setupRoutePathSelect();
  if (typeof render === 'function') render();
  const beforeItems = menuItems();
  const continuationIndex = beforeItems.findIndex((item) => item.command === 'continueRouteAssistPath');
  const continuationItem = beforeItems[continuationIndex] || null;
  const record = {
    continuationIndex,
    continuationItem,
    beforeLabels: beforeItems.map((item) => menuItemLabel(item)),
    beforeMap: map?.name || '',
    beforeTile: footTile(),
    beforeSearch: window.location.search,
    beforeTrialTransitions: trialTransitions,
    beforeSelectedRouteGoal: selectedRouteGoal,
    beforeRoutePathValue: routePathSelect.value || '',
    beforeRouteNextText: routeNextButton.textContent || '',
    beforeRouteNextTitle: routeNextButton.title || '',
    beforeRouteContinuationTarget: routeNextButton.dataset.routeContinuationTarget || '',
    beforeHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  };
  selectedMenuItemIndex = continuationIndex >= 0 ? continuationIndex : 0;
  const used = useSelectedMenuItem();
  sessionStorage.setItem(storageKey, JSON.stringify({ ...record, used }));
} catch (error) {
  sessionStorage.setItem(storageKey, JSON.stringify({ error: String(error && error.message || error) }));
}
return true;
"""


def route_continuation_menu_state_script() -> str:
    return """
const storageKey = "__hwanseCandidateRouteContinuationMenu";
let record = {};
try {
  record = JSON.parse(sessionStorage.getItem(storageKey) || "{}");
} catch (error) {
  record = { error: String(error && error.message || error) };
}
try {
  if (typeof render === 'function') render();
  const currentItems = typeof menuItems === 'function' ? menuItems() : [];
  return {
    ...record,
    readyState: document.readyState,
    scene: typeof scene === 'undefined' ? '' : scene,
    map: typeof map === 'undefined' || !map ? '' : map.name,
    tile: typeof footTile === 'function' ? footTile() : null,
    search: window.location.search,
    transitionTargetParam: new URLSearchParams(window.location.search).get('transitionTarget') || '',
    routeGoalParam: new URLSearchParams(window.location.search).get('routeGoal') || '',
    routeAutoSaveParam: new URLSearchParams(window.location.search).get('routeAutoSave') || '',
    trialTransitions: typeof trialTransitions === 'undefined' ? '' : trialTransitions,
    selectedRouteGoal: typeof selectedRouteGoal === 'undefined' ? '' : selectedRouteGoal,
    selectedTransitionTarget: typeof selectedTransitionTarget === 'undefined' ? '' : selectedTransitionTarget,
    routePathValue: document.getElementById('routePathSelect')?.value || '',
    routePathLabels: [...(document.getElementById('routePathSelect')?.options || [])].map((option) => option.textContent || ''),
    routeNextText: document.getElementById('routeNextButton')?.textContent || '',
    routeNextTitle: document.getElementById('routeNextButton')?.title || '',
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []),
    currentLabels: currentItems.map((item) => menuItemLabel(item)),
    currentCommands: currentItems.map((item) => ({
      name: menuItemLabel(item),
      command: item.command || '',
      usable: item.usable !== false,
      routeTarget: item.routeTarget || '',
      routeNextTarget: item.routeNextTarget || '',
    })),
  };
} catch (error) {
  return { ...record, error: String(error && error.message || error) };
}
"""


def route_next_continuation_button_script() -> str:
    return """
const storageKey = "__hwanseCandidateRouteNextContinuationButton";
try {
  activeDialogue = null;
  menuOpen = false;
  if (typeof setupRoutePathSelect === 'function') setupRoutePathSelect();
  if (typeof render === 'function') render();
  window.HWANSE_SOUND_LOG = [];
  window.HWANSE_SOUND_COUNTS = {};
  window.HWANSE_LAST_SOUND = null;
  window.HWANSE_ROUTE_CONTROL_SOUND_LOG = [];
  window.HWANSE_LAST_ROUTE_CONTROL_SOUND = null;
  sessionStorage.removeItem('HWANSE_LAST_ROUTE_CONTROL_ACTION');
  sessionStorage.removeItem('HWANSE_LAST_ROUTE_CONTROL_SOUND');
  const beforeRecord = {
    beforeMap: map?.name || '',
    beforeTile: footTile(),
    beforeSearch: window.location.search,
    beforeTrialTransitions: trialTransitions,
    beforeSelectedRouteGoal: selectedRouteGoal,
    beforeRoutePathValue: routePathSelect.value || '',
    beforeRouteNextText: routeNextButton.textContent || '',
    beforeRouteNextTitle: routeNextButton.title || '',
    beforeRouteContinuationTarget: routeNextButton.dataset.routeContinuationTarget || '',
    beforeHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  };
  const activated = typeof activateRouteNextButton === 'function'
    ? activateRouteNextButton()
    : activateNextRoutePath();
  sessionStorage.setItem(storageKey, JSON.stringify({ ...beforeRecord, activated }));
} catch (error) {
  sessionStorage.setItem(storageKey, JSON.stringify({ error: String(error && error.message || error) }));
}
return true;
"""


def route_next_continuation_button_state_script() -> str:
    return """
const storageKey = "__hwanseCandidateRouteNextContinuationButton";
let record = {};
try {
  record = JSON.parse(sessionStorage.getItem(storageKey) || "{}");
} catch (error) {
  record = { error: String(error && error.message || error) };
}
let routeControlAction = null;
try {
  routeControlAction = JSON.parse(sessionStorage.getItem('HWANSE_LAST_ROUTE_CONTROL_ACTION') || "null");
} catch (error) {
  routeControlAction = null;
}
let routeControlSound = null;
try {
  routeControlSound = JSON.parse(sessionStorage.getItem('HWANSE_LAST_ROUTE_CONTROL_SOUND') || "null");
} catch (error) {
  routeControlSound = null;
}
let routeAssistInPlaceTransition = null;
try {
  routeAssistInPlaceTransition = JSON.parse(sessionStorage.getItem('HWANSE_LAST_ROUTE_ASSIST_IN_PLACE_TRANSITION') || "null");
} catch (error) {
  routeAssistInPlaceTransition = null;
}
try {
  if (typeof render === 'function') render();
  return {
    ...record,
    routeControlAction,
    routeControlSound,
    routeAssistInPlaceTransition: window.HWANSE_LAST_ROUTE_ASSIST_IN_PLACE_TRANSITION || routeAssistInPlaceTransition,
    readyState: document.readyState,
    scene: typeof scene === 'undefined' ? '' : scene,
    map: typeof map === 'undefined' || !map ? '' : map.name,
    tile: typeof footTile === 'function' ? footTile() : null,
    search: window.location.search,
    transitionTargetParam: new URLSearchParams(window.location.search).get('transitionTarget') || '',
    routeGoalParam: new URLSearchParams(window.location.search).get('routeGoal') || '',
    routeAutoSaveParam: new URLSearchParams(window.location.search).get('routeAutoSave') || '',
    trialTransitions: typeof trialTransitions === 'undefined' ? '' : trialTransitions,
    selectedRouteGoal: typeof selectedRouteGoal === 'undefined' ? '' : selectedRouteGoal,
    selectedTransitionTarget: typeof selectedTransitionTarget === 'undefined' ? '' : selectedTransitionTarget,
    routePathValue: document.getElementById('routePathSelect')?.value || '',
    routePathLabels: [...(document.getElementById('routePathSelect')?.options || [])].map((option) => option.textContent || ''),
    routeNextText: document.getElementById('routeNextButton')?.textContent || '',
    routeNextTitle: document.getElementById('routeNextButton')?.title || '',
    routeContinuationTarget: document.getElementById('routeNextButton')?.dataset.routeContinuationTarget || '',
    routeControlSoundLog: Array.isArray(window.HWANSE_ROUTE_CONTROL_SOUND_LOG) ? window.HWANSE_ROUTE_CONTROL_SOUND_LOG.slice() : [],
    routeControlSoundLogLength: Array.isArray(window.HWANSE_ROUTE_CONTROL_SOUND_LOG) ? window.HWANSE_ROUTE_CONTROL_SOUND_LOG.length : 0,
    lastRouteControlSound: window.HWANSE_LAST_ROUTE_CONTROL_SOUND || routeControlSound,
    soundCounts: { ...(window.HWANSE_SOUND_COUNTS || {}) },
    menuConfirmSoundCount: Number((window.HWANSE_SOUND_COUNTS || {}).menuConfirm || 0),
    lastSound: window.HWANSE_LAST_SOUND || null,
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []),
  };
} catch (error) {
  return { ...record, routeControlAction, routeControlSound, error: String(error && error.message || error) };
}
"""


def route_continuation_entry_script() -> str:
    return """
const storageKey = "__hwanseCandidateRouteContinuationEntry";
try {
  activeDialogue = null;
  if (typeof setupRoutePathSelect === 'function') setupRoutePathSelect();
  if (typeof render === 'function') render();
  const beforeRecord = {
    beforeMap: map?.name || '',
    beforeTile: footTile(),
    beforeSearch: window.location.search,
    beforeTrialTransitions: trialTransitions,
    beforeSelectedRouteGoal: selectedRouteGoal,
    beforeSelectedTransitionTarget: selectedTransitionTarget,
    beforeRoutePathValue: routePathSelect.value || '',
    beforeRouteNextText: routeNextButton.textContent || '',
    beforeRouteNextTitle: routeNextButton.title || '',
    beforeHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  };
  const entered = activateNextRoutePath();
  sessionStorage.setItem(storageKey, JSON.stringify({ ...beforeRecord, entered }));
} catch (error) {
  sessionStorage.setItem(storageKey, JSON.stringify({ error: String(error && error.message || error) }));
}
return true;
"""


def route_continuation_entry_state_script() -> str:
    return """
const storageKey = "__hwanseCandidateRouteContinuationEntry";
let record = {};
try {
  record = JSON.parse(sessionStorage.getItem(storageKey) || "{}");
} catch (error) {
  record = { error: String(error && error.message || error) };
}
let payload = null;
try {
  payload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
} catch (error) {
  payload = null;
}
let routeAssistInPlaceTransition = null;
try {
  routeAssistInPlaceTransition = JSON.parse(sessionStorage.getItem('HWANSE_LAST_ROUTE_ASSIST_IN_PLACE_TRANSITION') || "null");
} catch (error) {
  routeAssistInPlaceTransition = null;
}
try {
  if (typeof render === 'function') render();
  const currentItems = typeof menuItems === 'function' ? menuItems() : [];
  const nextContinuationItem = currentItems.find((item) => item.command === 'continueRouteAssistPath') || null;
  return {
    ...record,
    readyState: document.readyState,
    scene: typeof scene === 'undefined' ? '' : scene,
    map: typeof map === 'undefined' || !map ? '' : map.name,
    tile: typeof footTile === 'function' ? footTile() : null,
    search: window.location.search,
	    routeGoalParam: new URLSearchParams(window.location.search).get('routeGoal') || '',
	    routeAutoSaveParam: new URLSearchParams(window.location.search).get('routeAutoSave') || '',
	    routeStepSourceParam: new URLSearchParams(window.location.search).get('routeStepSource') || '',
	    routeStepTargetParam: new URLSearchParams(window.location.search).get('routeStepTarget') || '',
	    routeStepKindParam: new URLSearchParams(window.location.search).get('routeStepKind') || '',
	    trialTransitions: typeof trialTransitions === 'undefined' ? '' : trialTransitions,
	    selectedRouteGoal: typeof selectedRouteGoal === 'undefined' ? '' : selectedRouteGoal,
	    routePathValue: document.getElementById('routePathSelect')?.value || '',
	    routePathLabels: [...(document.getElementById('routePathSelect')?.options || [])].map((option) => option.textContent || ''),
	    routeNextText: document.getElementById('routeNextButton')?.textContent || '',
    routeNextTitle: document.getElementById('routeNextButton')?.title || '',
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []),
    currentLabels: currentItems.map((item) => menuItemLabel(item)),
    nextContinuationItem,
    fieldEncounter: typeof fieldEncounterSavePayload === 'function' ? fieldEncounterSavePayload() : null,
    fieldEncounterMenuLabel: typeof fieldEncounterMenuLabel === 'function' ? fieldEncounterMenuLabel() : '',
    routeAssistAutoSave: window.HWANSE_LAST_ROUTE_ASSIST_AUTO_SAVE || null,
    routeAssistInPlaceTransition: window.HWANSE_LAST_ROUTE_ASSIST_IN_PLACE_TRANSITION || routeAssistInPlaceTransition,
    routeProgressFeedbackLog: window.HWANSE_ROUTE_PROGRESS_FEEDBACK_LOG || [],
    routeProgressFeedbackRender: window.HWANSE_ROUTE_PROGRESS_FEEDBACK_RENDER || [],
    routeProgressFeedbackLast: window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK || null,
    routeProgressFeedbackLastRender: window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK_RENDER || null,
    progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
    progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
    savedProgress: payload?.prototypeProgress || null,
    savedFieldEncounter: payload?.fieldEncounter || null,
    savedPayloadMap: payload?.map || '',
    savedPayloadTile: payload?.tile || null,
    savedRouteGoal: payload?.routeState?.selectedRouteGoal || '',
	    savedTrialTransitions: payload?.routeState?.trialTransitions || '',
	    savedRouteAutoSaveSource: window.HWANSE_LAST_ROUTE_ASSIST_AUTO_SAVE?.source || '',
  };
} catch (error) {
  return { ...record, error: String(error && error.message || error) };
}
"""


def route_source_prompt_script() -> str:
    return """
window.__hwanseCandidateRouteSourcePrompt = null;
try {
  const raw = localStorage.getItem(RUNTIME_SAVE_KEY) || '{}';
  const payload = JSON.parse(raw);
  prototypeProgress = createPrototypeProgressState(payload.prototypeProgress);
  publishPrototypeProgress();
  trialTransitions = 'routeAssist';
  selectedTransitionTarget = 'map2_02d';
  pendingTransitionTarget = 'map2_02d';
  transitionTargetExplicit = true;
  setupRouteFrontierSelect();
  setupRoutePathSelect();
  render();
  window.__hwanseCandidateRouteSourcePrompt = {
    scene,
    map: map?.name || '',
    tile: footTile(),
    search: window.location.search,
    prompt: window.HWANSE_LAST_ACTION_PROMPT || null,
    progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
    savedProgress: payload.prototypeProgress || null,
    routePathLabels: [...routePathSelect.options].map((option) => option.textContent || ''),
    routeNextText: routeNextButton.textContent || '',
    routeNextTitle: routeNextButton.title || '',
  };
} catch (error) {
  window.__hwanseCandidateRouteSourcePrompt = { error: String(error && error.message || error) };
}
return true;
"""


def route_source_prompt_state_script() -> str:
    return "return window.__hwanseCandidateRouteSourcePrompt || null;"


def continue_route_progress_script() -> str:
    return """
window.__hwanseCandidateRouteProgressChain = null;
activeDialogue = null;
window.HWANSE_LAST_ROUTE_CANDIDATE_AUTO_SAVE = null;
window.HWANSE_ROUTE_PROGRESS_FEEDBACK_LOG = [];
window.HWANSE_ROUTE_PROGRESS_FEEDBACK_RENDER = [];
window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK = null;
window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK_RENDER = null;
trialTransitions = 'routeAssist';
setupRouteFrontierSelect();
const option = [...routeFrontierSelect.options].find((item) => item.dataset.transitionTarget === 'map2_18d') || null;
const activated = activateRouteFrontierOption(option);
const before = {
  activated,
  scene,
  map: map?.name || '',
  tile: footTile(),
  target: option?.dataset.transitionTarget || '',
  frontierValue: option?.value || '',
  targets: transitionTargetsAtFoot(),
};
const handled = checkMapExitTrialTransition(0, -1);
const deadline = performance.now() + 6000;
function finishWhenReady() {
  const autoSave = window.HWANSE_LAST_ROUTE_CANDIDATE_AUTO_SAVE || null;
  if (map?.name === 'map2_18d' && autoSave?.saved === true) {
    render();
    const raw = localStorage.getItem(RUNTIME_SAVE_KEY) || '{}';
    const payload = JSON.parse(raw);
    startPrototypeProgressReview();
    window.__hwanseCandidateRouteProgressChain = {
      handled,
      saved: autoSave?.saved === true,
      autoSave,
      before,
      scene,
      map: map?.name || '',
      tile: footTile(),
      search: window.location.search,
      trialTransitions,
      activeId: activeDialogue?.block?.blockId || '',
      activeLines: activeDialogue?.lines?.slice(0, 12) || [],
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
      savedProgress: payload.prototypeProgress || null,
      payloadMap: payload.map || '',
      payloadTile: payload.tile || null,
      labels: menuItems().map((item) => menuItemLabel(item)),
      routePathLabels: [...routePathSelect.options].map((option) => option.textContent || ''),
      routeNextText: routeNextButton.textContent || '',
      routeNextTitle: routeNextButton.title || '',
      routeProgressFeedbackLog: window.HWANSE_ROUTE_PROGRESS_FEEDBACK_LOG || [],
      routeProgressFeedbackRender: window.HWANSE_ROUTE_PROGRESS_FEEDBACK_RENDER || [],
      routeProgressFeedbackLast: window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK || null,
      routeProgressFeedbackLastRender: window.HWANSE_LAST_ROUTE_PROGRESS_FEEDBACK_RENDER || null,
    };
    return;
  }
  if (performance.now() >= deadline) {
    window.__hwanseCandidateRouteProgressChain = {
      error: `route candidate chain timed out at ${map?.name || ''}`,
      handled,
      before,
      map: map?.name || '',
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
    };
    return;
  }
  requestAnimationFrame(finishWhenReady);
}
finishWhenReady();
return true;
"""


def route_progress_chain_state_script() -> str:
    return "return window.__hwanseCandidateRouteProgressChain || 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 click_title_route_completion_gate_script() -> str:
    return """
window.__hwanseCandidateRouteTitleCompletionGate = null;
window.HWANSE_ROUTE_GATE_FEEDBACK_LOG = [];
window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER = [];
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK = null;
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER = null;
if (typeof activeRouteGateFeedbacks !== 'undefined') activeRouteGateFeedbacks = [];
try {
  if (typeof updateRuntimeSaveControls === 'function') updateRuntimeSaveControls();
  const items = typeof titleMenuItems === 'function' ? titleMenuItems() : [];
  const index = items.findIndex((item) => item.key === 'routeCompletion');
  if (index < 0) {
    window.__hwanseCandidateRouteTitleCompletionGate = {
      ok: false,
      reason: 'missing-routeCompletion',
      titleMenuKeys: items.map((item) => item.key),
      titleMenuLabels: items.map((item) => item.label),
    };
    return true;
  }
  selectedTitleMenuIndex = index;
  const activated = activateSelectedTitleMenuItem();
  window.__hwanseCandidateRouteTitleCompletionGate = {
    ok: Boolean(activated),
    activated: Boolean(activated),
    selectedTitleMenuIndex,
    titleMenuKeys: items.map((item) => item.key),
    titleMenuLabels: items.map((item) => item.label),
    titleItem: items[index],
  };
} catch (error) {
  window.__hwanseCandidateRouteTitleCompletionGate = { ok: false, error: String(error && error.message || error) };
}
return true;
"""


def title_route_completion_gate_state_script() -> str:
    return """
if (typeof render === 'function') render();
const payload = typeof readRuntimeSavePayload === 'function' ? readRuntimeSavePayload() : null;
const titlePayload = typeof titleRouteCompletionPayload === 'function' ? titleRouteCompletionPayload() : null;
const titleGate = window.HWANSE_LAST_TITLE_ROUTE_COMPLETION_GATE || null;
const notice = window.HWANSE_LAST_ROUTE_COMPLETION_NOTICE || null;
return {
  click: window.__hwanseCandidateRouteTitleCompletionGate || null,
  titleGate,
  notice,
  scene: typeof scene === 'undefined' ? '' : scene,
  map: typeof map === 'undefined' || !map ? '' : map.name,
  activeId: activeDialogue?.block?.blockId || '',
  activeLines: activeDialogue?.lines?.slice(0, 8) || [],
  completion: typeof publishPrototypeCompletionState === 'function' ? publishPrototypeCompletionState() : null,
  routeGateFeedbackLog: window.HWANSE_ROUTE_GATE_FEEDBACK_LOG || [],
  routeGateFeedbackRender: window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER || [],
  routeGateFeedbackLast: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK || null,
  routeGateFeedbackLastRender: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER || null,
  playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  payloadMap: payload?.map || '',
  payloadCounts: payload?.prototypeProgress?.counts || {},
  payloadStoryFlags: payload?.prototypeStoryFlags || null,
  titleRouteCompletionPayload: titlePayload,
};
"""


def click_title_route_clear_gate_script() -> str:
    return """
window.__hwanseCandidateRouteTitleClearGate = null;
window.HWANSE_ROUTE_GATE_FEEDBACK_LOG = [];
window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER = [];
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK = null;
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER = null;
if (typeof activeRouteGateFeedbacks !== 'undefined') activeRouteGateFeedbacks = [];
try {
  if (typeof updateRuntimeSaveControls === 'function') updateRuntimeSaveControls();
  const items = typeof titleMenuItems === 'function' ? titleMenuItems() : [];
  const index = items.findIndex((item) => item.key === 'routeClear');
  if (index < 0) {
    window.__hwanseCandidateRouteTitleClearGate = {
      ok: false,
      reason: 'missing-routeClear',
      titleMenuKeys: items.map((item) => item.key),
      titleMenuLabels: items.map((item) => item.label),
    };
    return true;
  }
  selectedTitleMenuIndex = index;
  const activated = activateSelectedTitleMenuItem();
  window.__hwanseCandidateRouteTitleClearGate = {
    ok: Boolean(activated),
    activated: Boolean(activated),
    selectedTitleMenuIndex,
    titleMenuKeys: items.map((item) => item.key),
    titleMenuLabels: items.map((item) => item.label),
    titleItem: items[index],
  };
} catch (error) {
  window.__hwanseCandidateRouteTitleClearGate = { ok: false, error: String(error && error.message || error) };
}
return true;
"""


def title_route_clear_gate_state_script() -> str:
    return """
if (typeof render === 'function') render();
const payload = typeof readRuntimeSavePayload === 'function' ? readRuntimeSavePayload() : null;
const titlePayload = typeof titleRouteClearPayload === 'function' ? titleRouteClearPayload() : null;
const titleGate = window.HWANSE_LAST_TITLE_ROUTE_CLEAR_GATE || null;
const summary = window.HWANSE_LAST_ROUTE_CLEAR_SUMMARY || null;
const progressReviewBlock = typeof prototypeProgressReviewBlock === 'function'
  ? prototypeProgressReviewBlock()
  : null;
const menuCommands = (typeof menuItems === 'function' ? menuItems() : []).map((item) => ({
  name: typeof menuItemLabel === 'function' ? menuItemLabel(item) : (item.name || ''),
  command: item.command || '',
  usable: item.usable !== false,
  routeTarget: item.routeTarget || '',
}));
return {
  click: window.__hwanseCandidateRouteTitleClearGate || null,
  titleGate,
  summary,
  scene: typeof scene === 'undefined' ? '' : scene,
  map: typeof map === 'undefined' || !map ? '' : map.name,
  activeId: activeDialogue?.block?.blockId || '',
  activeLines: activeDialogue?.lines?.slice(0, 8) || [],
  completion: typeof publishPrototypeCompletionState === 'function' ? publishPrototypeCompletionState() : null,
  routeGateFeedbackLog: window.HWANSE_ROUTE_GATE_FEEDBACK_LOG || [],
  routeGateFeedbackRender: window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER || [],
  routeGateFeedbackLast: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK || null,
  routeGateFeedbackLastRender: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER || null,
  playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  payloadMap: payload?.map || '',
  payloadCounts: payload?.prototypeProgress?.counts || {},
  payloadStoryFlags: payload?.prototypeStoryFlags || null,
  titleRouteClearPayload: titlePayload,
  titleContinueLabel: typeof titleContinueLabel === 'function' ? titleContinueLabel() : '',
  progressReviewBlock,
  menuCommands,
};
"""


def continued_route_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,
  trialTransitions: typeof trialTransitions === 'undefined' ? '' : trialTransitions,
  selectedRouteGoal: typeof selectedRouteGoal === 'undefined' ? '' : selectedRouteGoal,
  routePathValue: document.getElementById('routePathSelect')?.value || '',
  routeNextText: document.getElementById('routeNextButton')?.textContent || '',
  routeNextTitle: document.getElementById('routeNextButton')?.title || '',
  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,
};
"""


def title_continue_route_restore_script() -> str:
    return """
window.__hwanseCandidateRouteTitleContinueRestore = null;
try {
  startPrototypeProgressReview();
  render();
  window.__hwanseCandidateRouteTitleContinueRestore = {
    titleContinue: true,
    loaded: true,
    scene,
    map: map?.name || '',
    tile: footTile(),
    search: window.location.search,
    trialTransitions,
    selectedRouteGoal,
    routePathValue: routePathSelect.value || '',
    quickLoadHidden: document.getElementById('quickLoadButton')?.hidden ?? null,
    quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
    activeId: activeDialogue?.block?.blockId || '',
    activeLines: activeDialogue?.lines?.slice(0, 12) || [],
    progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
    labels: menuItems().map((item) => menuItemLabel(item)),
    routePathLabels: [...routePathSelect.options].map((option) => option.textContent || ''),
    routeNextText: routeNextButton.textContent || '',
    routeNextTitle: routeNextButton.title || '',
  };
} catch (error) {
  window.__hwanseCandidateRouteTitleContinueRestore = { error: String(error && error.message || error) };
}
return true;
"""


def title_continue_route_restore_state_script() -> str:
    return "return window.__hwanseCandidateRouteTitleContinueRestore || null;"


def route_continuation_title_restore_script() -> str:
    return """
window.__hwanseCandidateRouteContinuationTitleRestore = null;
try {
  if (typeof render === 'function') render();
  const items = typeof menuItems === 'function' ? menuItems() : [];
  const continuationItem = items.find((item) => item.command === 'continueRouteAssistPath') || null;
  const completionItem = items.find((item) => item.command === 'showRouteCompletionNotice') || null;
  const clearItem = items.find((item) => item.command === 'showRouteClearSummary') || null;
  let payload = null;
  try {
    payload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
  } catch (error) {
    payload = null;
  }
  window.__hwanseCandidateRouteContinuationTitleRestore = {
    titleContinue: true,
    readyState: document.readyState,
    scene,
    map: map?.name || '',
    tile: footTile(),
    search: window.location.search,
    trialTransitions,
    selectedRouteGoal,
    routePathValue: routePathSelect.value || '',
    routeNextText: routeNextButton.textContent || '',
    routeNextTitle: routeNextButton.title || '',
    quickLoadHidden: document.getElementById('quickLoadButton')?.hidden ?? null,
    quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
    labels: items.map((item) => menuItemLabel(item)),
    routePathLabels: [...routePathSelect.options].map((option) => option.textContent || ''),
    continuationItem,
    completionItem,
    clearItem,
    fieldEncounter: typeof fieldEncounterSavePayload === 'function' ? fieldEncounterSavePayload() : null,
	    fieldEncounterMenuLabel: typeof fieldEncounterMenuLabel === 'function' ? fieldEncounterMenuLabel() : '',
	    progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
	    savedProgress: payload?.prototypeProgress || null,
	    savedStoryFlags: payload?.prototypeStoryFlags || null,
	    completion: typeof publishPrototypeCompletionState === 'function' ? publishPrototypeCompletionState() : null,
	    savedFieldEncounter: payload?.fieldEncounter || null,
	    savedPayloadMap: payload?.map || '',
	    savedPayloadTile: payload?.tile || null,
	    savedRouteGoal: payload?.routeState?.selectedRouteGoal || '',
	    savedTrialTransitions: payload?.routeState?.trialTransitions || '',
	  };
} catch (error) {
  window.__hwanseCandidateRouteContinuationTitleRestore = { error: String(error && error.message || error) };
}
return true;
"""


def route_continuation_title_restore_state_script() -> str:
    return "return window.__hwanseCandidateRouteContinuationTitleRestore || null;"


def route_continuation_field_encounter_script() -> str:
    return """
window.__hwanseCandidateRouteFieldEncounter = null;
Promise.all([ensureBattleData()])
  .then(() => {
    const before = {
      scene,
      mapName: map?.name || '',
      foot: footTile(),
      routeState: runtimeRouteStateSavePayload(),
      fieldEncounter: fieldEncounterSavePayload(),
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
      action: battleCandidateActionState(),
    };
    const movementInputs = [
      { code: 'ArrowRight', key: 'ArrowRight', dx: 1, dy: 0 },
      { code: 'ArrowLeft', key: 'ArrowLeft', dx: -1, dy: 0 },
      { code: 'ArrowUp', key: 'ArrowUp', dx: 0, dy: -1 },
      { code: 'ArrowDown', key: 'ArrowDown', dx: 0, dy: 1 },
    ];
    const selectedInput = movementInputs
      .map((input) => ({ ...input, movement: chooseMovementStep(before.foot, input.dx, input.dy) }))
      .find((input) => input.movement);
    if (!selectedInput) {
      window.__hwanseCandidateRouteFieldEncounter = {
        error: 'missing-passable-target-movement',
        before,
      };
      return;
    }
    fieldEncounterState = createFieldEncounterState({
      enabled: true,
      stepCount: FIELD_ENCOUNTER_STEP_THRESHOLD - 1,
      lastMap: map?.name || '',
    });
    syncMapQuery();
    updateBattleButton();
    refreshPlayHudLines();
    const armed = {
      fieldEncounter: fieldEncounterSavePayload(),
      search: location.search,
      buttonText: document.getElementById('battleButton')?.textContent || '',
      playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
      action: battleCandidateActionState(),
    };
    window.__hwanseCandidateRouteFieldEncounter = {
      pending: true,
      movementInput: true,
      inputCode: selectedInput.code,
      before,
      armed,
      plannedMovement: {
        nextTile: selectedInput.movement.nextTile || null,
        target: selectedInput.movement.target || null,
      },
    };
    window.dispatchEvent(new KeyboardEvent('keydown', {
      bubbles: true,
      cancelable: true,
      code: selectedInput.code,
      key: selectedInput.key,
    }));
    window.setTimeout(() => {
      window.dispatchEvent(new KeyboardEvent('keyup', {
        bubbles: true,
        cancelable: true,
        code: selectedInput.code,
        key: selectedInput.key,
      }));
    }, 80);
    const startedAt = performance.now();
    const finish = (started, movement) => {
      const battleStartSnapshot = {
        scene,
        mapName: map?.name || '',
        summary: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
        battleCandidate: battleState?.candidate ? {
          id: battleState.candidate.id || '',
          blockId: battleState.candidate.blockId || '',
          battleBackground: battleState.candidate.battleBackground || '',
        } : null,
      };
      let victoryResult = null;
      let closeResult = null;
      let victorySummary = null;
      if (started && battleState) {
        battleState.enemy.hp = 1;
        victoryResult = useSelectedBattleCommand();
        victorySummary = window.HWANSE_LAST_BATTLE_PROTOTYPE || null;
        closeResult = useSelectedBattleCommand();
        updateBattleButton();
      }
      if (typeof render === 'function') render();
      const victoryAutoSave = window.HWANSE_LAST_FIELD_ENCOUNTER_VICTORY_SAVE || null;
      let savedPayload = null;
      try {
        savedPayload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || 'null');
      } catch (error) {
        savedPayload = null;
      }
      window.__hwanseCandidateRouteFieldEncounter = {
        pending: false,
        started,
        before,
        armed,
        movementInput: true,
        inputCode: selectedInput.code,
        plannedMovement: {
          nextTile: selectedInput.movement.nextTile || null,
          target: selectedInput.movement.target || null,
        },
        movement,
        startSnapshot: battleStartSnapshot,
        victoryResult,
        closeResult,
        victorySummary,
        victoryAutoSave,
        savedPayload,
        scene,
        mapName: map?.name || '',
        foot: footTile(),
        routeState: runtimeRouteStateSavePayload(),
        fieldEncounter: fieldEncounterSavePayload(),
        progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
        progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
        completion: publishPrototypeCompletionState(),
        buttonText: document.getElementById('battleButton')?.textContent || '',
        quickLoadHidden: document.getElementById('quickLoadButton')?.hidden ?? null,
        quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
        routePathValue: document.getElementById('routePathSelect')?.value || '',
        routeNextText: document.getElementById('routeNextButton')?.textContent || '',
        routeNextTitle: document.getElementById('routeNextButton')?.title || '',
        labels: (typeof menuItems === 'function' ? menuItems() : []).map((item) => menuItemLabel(item)),
        playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
        runtimeState: runtimeState ? {
          money: runtimeState.money || 0,
          items: (runtimeState.items || []).map((item) => `${item.key}:${item.count || 0}`),
        } : null,
        originalRoutePromotionImplemented: false,
        originalEncounterRuntimeImplemented: false,
      };
    };
    const poll = () => {
      const afterFoot = footTile();
      const movement = {
        tileChanged: before.foot?.x !== afterFoot?.x || before.foot?.y !== afterFoot?.y,
        beforeFoot: before.foot || null,
        afterFoot,
        moving: !!player.moving,
        activeStep: !!player.step,
        scene,
      };
      if (scene === 'battle' && battleState) {
        finish(true, movement);
      } else if (performance.now() - startedAt > 3500) {
        finish(false, movement);
      } else {
        window.setTimeout(poll, 50);
      }
    };
    window.setTimeout(poll, 120);
  })
  .catch((error) => {
    window.__hwanseCandidateRouteFieldEncounter = { error: String(error && error.message || error) };
  });
return true;
"""


def route_continuation_field_encounter_state_script() -> str:
    return "return window.__hwanseCandidateRouteFieldEncounter || null;"


def route_completion_notice_script() -> str:
    return """
window.__hwanseCandidateRouteCompletionNotice = null;
window.HWANSE_ROUTE_GATE_FEEDBACK_LOG = [];
window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER = [];
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK = null;
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER = null;
if (typeof activeRouteGateFeedbacks !== 'undefined') activeRouteGateFeedbacks = [];
try {
  if (typeof render === 'function') render();
  const button = document.getElementById('routeNextButton');
  const before = {
    scene,
    map: map?.name || '',
    tile: footTile(),
    routeNextHidden: button?.hidden ?? null,
    routeNextDisabled: button?.disabled ?? null,
    routeNextText: button?.textContent || '',
    routeNextTitle: button?.title || '',
    routeCompleted: button?.dataset?.routeCompleted || '',
    completion: typeof publishPrototypeCompletionState === 'function' ? publishPrototypeCompletionState() : null,
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  };
  const objectiveActionResult = typeof activatePrototypeObjectiveAction === 'function'
    ? activatePrototypeObjectiveAction()
    : false;
  const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
  const objectiveActiveId = activeDialogue?.block?.blockId || '';
  const objectiveActiveLines = activeDialogue?.lines?.slice(0, 8) || [];
  activeDialogue = null;
  const activated = button ? button.click() !== false : false;
  const buttonNotice = window.HWANSE_LAST_ROUTE_COMPLETION_NOTICE || null;
  activeDialogue = null;
  menuMode = 'main';
  const menuRows = typeof menuItems === 'function' ? menuItems() : [];
  const menuCommands = menuRows.map((item) => ({
    name: typeof menuItemLabel === 'function' ? menuItemLabel(item) : (item.name || ''),
    command: item.command || '',
    usable: item.usable !== false,
    routeTarget: item.routeTarget || '',
  }));
  const completionMenuIndex = menuRows.findIndex((item) => item.command === 'showRouteCompletionNotice');
  selectedMenuItemIndex = completionMenuIndex >= 0 ? completionMenuIndex : 0;
  const menuCompletionResult = completionMenuIndex >= 0 && typeof useSelectedMenuItem === 'function'
    ? useSelectedMenuItem()
    : false;
  const notice = window.HWANSE_LAST_ROUTE_COMPLETION_NOTICE || null;
  const progressReviewBlock = typeof prototypeProgressReviewBlock === 'function'
    ? prototypeProgressReviewBlock()
    : null;
  if (typeof render === 'function') render();
  window.__hwanseCandidateRouteCompletionNotice = {
    activated,
    objectiveActionResult,
    objectiveAction,
    objectiveActiveId,
    objectiveActiveLines,
    buttonNotice,
    menuCompletionResult,
    menuCommands,
    menuActiveId: activeDialogue?.block?.blockId || '',
    menuActiveLines: activeDialogue?.lines?.slice(0, 8) || [],
    progressReviewBlock,
    before,
    scene,
    map: map?.name || '',
    activeId: activeDialogue?.block?.blockId || '',
    activeLines: activeDialogue?.lines?.slice(0, 8) || [],
    notice,
    completion: typeof publishPrototypeCompletionState === 'function' ? publishPrototypeCompletionState() : null,
    routeGateFeedbackLog: window.HWANSE_ROUTE_GATE_FEEDBACK_LOG || [],
    routeGateFeedbackRender: window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER || [],
    routeGateFeedbackLast: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK || null,
    routeGateFeedbackLastRender: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER || null,
    routeNextText: button?.textContent || '',
    routeNextTitle: button?.title || '',
    routeCompleted: button?.dataset?.routeCompleted || '',
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  };
} catch (error) {
  window.__hwanseCandidateRouteCompletionNotice = { error: String(error && error.message || error) };
}
return true;
"""


def route_completion_notice_state_script() -> str:
    return "return window.__hwanseCandidateRouteCompletionNotice || null;"


def restored_route_completion_menu_notice_script() -> str:
    return """
window.__hwanseCandidateRouteRestoredCompletionMenuNotice = null;
window.HWANSE_ROUTE_GATE_FEEDBACK_LOG = [];
window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER = [];
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK = null;
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER = null;
if (typeof activeRouteGateFeedbacks !== 'undefined') activeRouteGateFeedbacks = [];
try {
  activeDialogue = null;
  menuMode = 'main';
  if (typeof render === 'function') render();
  const menuRows = typeof menuItems === 'function' ? menuItems() : [];
  const menuCommands = menuRows.map((item) => ({
    name: typeof menuItemLabel === 'function' ? menuItemLabel(item) : (item.name || ''),
    command: item.command || '',
    usable: item.usable !== false,
    routeTarget: item.routeTarget || '',
  }));
  const completionMenuIndex = menuRows.findIndex((item) => item.command === 'showRouteCompletionNotice');
  selectedMenuItemIndex = completionMenuIndex >= 0 ? completionMenuIndex : 0;
  const result = completionMenuIndex >= 0 && typeof useSelectedMenuItem === 'function'
    ? useSelectedMenuItem()
    : false;
  const notice = window.HWANSE_LAST_ROUTE_COMPLETION_NOTICE || null;
  const progressReviewBlock = typeof prototypeProgressReviewBlock === 'function'
    ? prototypeProgressReviewBlock()
    : null;
  if (typeof render === 'function') render();
  window.__hwanseCandidateRouteRestoredCompletionMenuNotice = {
    result,
    menuCommands,
    activeId: activeDialogue?.block?.blockId || '',
    activeLines: activeDialogue?.lines?.slice(0, 8) || [],
    notice,
    progressReviewBlock,
    completion: typeof publishPrototypeCompletionState === 'function' ? publishPrototypeCompletionState() : null,
    routeGateFeedbackLog: window.HWANSE_ROUTE_GATE_FEEDBACK_LOG || [],
    routeGateFeedbackRender: window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER || [],
    routeGateFeedbackLast: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK || null,
    routeGateFeedbackLastRender: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER || null,
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  };
} catch (error) {
  window.__hwanseCandidateRouteRestoredCompletionMenuNotice = { error: String(error && error.message || error) };
}
return true;
"""


def restored_route_completion_menu_notice_state_script() -> str:
    return "return window.__hwanseCandidateRouteRestoredCompletionMenuNotice || null;"


def restored_route_clear_menu_summary_script() -> str:
    return """
window.__hwanseCandidateRouteRestoredClearMenuSummary = null;
window.HWANSE_ROUTE_GATE_FEEDBACK_LOG = [];
window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER = [];
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK = null;
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER = null;
if (typeof activeRouteGateFeedbacks !== 'undefined') activeRouteGateFeedbacks = [];
try {
  activeDialogue = null;
  menuMode = 'main';
  if (typeof render === 'function') render();
  const menuRows = typeof menuItems === 'function' ? menuItems() : [];
  const menuCommands = menuRows.map((item) => ({
    name: typeof menuItemLabel === 'function' ? menuItemLabel(item) : (item.name || ''),
    command: item.command || '',
    usable: item.usable !== false,
    routeTarget: item.routeTarget || '',
  }));
  const clearMenuIndex = menuRows.findIndex((item) => item.command === 'showRouteClearSummary');
  selectedMenuItemIndex = clearMenuIndex >= 0 ? clearMenuIndex : 0;
  const result = clearMenuIndex >= 0 && typeof useSelectedMenuItem === 'function'
    ? useSelectedMenuItem()
    : false;
  const summary = window.HWANSE_LAST_ROUTE_CLEAR_SUMMARY || null;
  const progressReviewBlock = typeof prototypeProgressReviewBlock === 'function'
    ? prototypeProgressReviewBlock()
    : null;
  if (typeof render === 'function') render();
  window.__hwanseCandidateRouteRestoredClearMenuSummary = {
    result,
    menuCommands,
    activeId: activeDialogue?.block?.blockId || '',
    activeLines: activeDialogue?.lines?.slice(0, 8) || [],
    summary,
    progressReviewBlock,
    completion: typeof publishPrototypeCompletionState === 'function' ? publishPrototypeCompletionState() : null,
    routeGateFeedbackLog: window.HWANSE_ROUTE_GATE_FEEDBACK_LOG || [],
    routeGateFeedbackRender: window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER || [],
    routeGateFeedbackLast: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK || null,
    routeGateFeedbackLastRender: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER || null,
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  };
} catch (error) {
  window.__hwanseCandidateRouteRestoredClearMenuSummary = { error: String(error && error.message || error) };
}
return true;
"""


def restored_route_clear_menu_summary_state_script() -> str:
    return "return window.__hwanseCandidateRouteRestoredClearMenuSummary || null;"


def route_clear_controls_script() -> str:
    return """
window.__hwanseCandidateRouteClearControls = null;
window.HWANSE_ROUTE_GATE_FEEDBACK_LOG = [];
window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER = [];
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK = null;
window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER = null;
if (typeof activeRouteGateFeedbacks !== 'undefined') activeRouteGateFeedbacks = [];
try {
  activeDialogue = null;
  if (typeof render === 'function') render();
  const button = document.getElementById('routeNextButton');
  const before = {
    scene,
    map: map?.name || '',
    tile: footTile(),
    routeNextText: button?.textContent || '',
    routeNextTitle: button?.title || '',
    routeCompleted: button?.dataset?.routeCompleted || '',
    routeCleared: button?.dataset?.routeCleared || '',
    objective: typeof prototypeObjectiveState === 'function' ? prototypeObjectiveState() : null,
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  };
  const objectiveResult = typeof activatePrototypeObjectiveAction === 'function'
    ? activatePrototypeObjectiveAction()
    : false;
  const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
  const objectiveSummary = window.HWANSE_LAST_ROUTE_CLEAR_SUMMARY || null;
  const objectiveActiveId = activeDialogue?.block?.blockId || '';
  const objectiveActiveLines = activeDialogue?.lines?.slice(0, 8) || [];
  activeDialogue = null;
  const buttonResult = button ? (button.click(), true) : false;
  const buttonSummary = window.HWANSE_LAST_ROUTE_CLEAR_SUMMARY || null;
  const buttonActiveId = activeDialogue?.block?.blockId || '';
  const buttonActiveLines = activeDialogue?.lines?.slice(0, 8) || [];
  const progressReviewBlock = typeof prototypeProgressReviewBlock === 'function'
    ? prototypeProgressReviewBlock()
    : null;
  if (typeof render === 'function') render();
  window.__hwanseCandidateRouteClearControls = {
    before,
    objectiveResult,
    objectiveAction,
    objectiveSummary,
    objectiveActiveId,
    objectiveActiveLines,
    buttonResult,
    buttonSummary,
    buttonActiveId,
    buttonActiveLines,
    progressReviewBlock,
    completion: typeof publishPrototypeCompletionState === 'function' ? publishPrototypeCompletionState() : null,
    routeGateFeedbackLog: window.HWANSE_ROUTE_GATE_FEEDBACK_LOG || [],
    routeGateFeedbackRender: window.HWANSE_ROUTE_GATE_FEEDBACK_RENDER || [],
    routeGateFeedbackLast: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK || null,
    routeGateFeedbackLastRender: window.HWANSE_LAST_ROUTE_GATE_FEEDBACK_RENDER || null,
    playHudLines: typeof playHudLines === 'function' ? playHudLines() : [],
  };
} catch (error) {
  window.__hwanseCandidateRouteClearControls = { error: String(error && error.message || error) };
}
return true;
"""


def route_clear_controls_state_script() -> str:
    return "return window.__hwanseCandidateRouteClearControls || null;"


def wait_for_default_prototype_map_exit_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, default_prototype_map_exit_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        progress = state.get("progress") or {}
        counts = progress.get("counts") or {}
        if (
            state
            and state.get("error") is None
            and state.get("map") == "map2_02d"
            and state.get("saved") is True
            and counts.get("route-candidate") == 1
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"default prototype map exit did not become ready: {state!r}")


def wait_for_route_progress_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, route_progress_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        progress = state.get("progress") or {}
        counts = progress.get("counts") or {}
        if state and state.get("error") is None and state.get("map") == "map2_02d" and counts.get("route-candidate") == 1:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route progress did not become ready: {state!r}")


def wait_for_route_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, route_progress_restore_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        progress = state.get("progress") or {}
        counts = progress.get("counts") or {}
        if (
            state
            and state.get("error") is None
            and state.get("loaded") is True
            and state.get("map") == "map2_02d"
            and state.get("activeId") == "prototype-progress:map2_02d"
            and counts.get("route-candidate") == 1
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route progress restore did not become ready: {state!r}")


def wait_for_route_source_prompt_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, route_source_prompt_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        prompt = state.get("prompt") or {}
        if state and state.get("error") is None and prompt.get("completed") is True:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route source prompt did not become ready: {state!r}")


def wait_for_route_chain_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, route_progress_chain_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        progress = state.get("progress") or {}
        counts = progress.get("counts") or {}
        if state and state.get("error") is None and state.get("map") == "map2_18d" and counts.get("route-candidate") == 2:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route progress chain did not become ready: {state!r}")


def wait_for_route_chain_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, route_progress_restore_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        progress = state.get("progress") or {}
        counts = progress.get("counts") or {}
        if (
            state
            and state.get("error") is None
            and state.get("loaded") is True
            and state.get("map") == "map2_18d"
            and state.get("activeId") == "prototype-progress:map2_18d"
            and counts.get("route-candidate") == 2
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route progress chain restore did not become ready: {state!r}")


def wait_for_route_menu_start_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, route_menu_start_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        next_item = state.get("nextItem") or {}
        if (
            state
            and state.get("error") is None
            and state.get("used") is True
            and state.get("trialTransitions") == "routeAssist"
            and state.get("selectedRouteGoal") == "map2_02d"
            and next_item.get("command") == "activateNextRoutePath"
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route menu start did not become ready: {state!r}")


def wait_for_route_goal_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, route_goal_menu_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        next_item = state.get("nextItem") or {}
        if (
            state
            and state.get("error") is None
            and state.get("opened") is True
            and state.get("selected") is True
            and state.get("trialTransitions") == "routeAssist"
            and state.get("selectedRouteGoal") == "map2_18d"
            and next_item.get("command") == "activateNextRoutePath"
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route goal menu did not become ready: {state!r}")


def wait_for_route_continuation_menu_state(
    port: int,
    session_id: str,
    expected_source: str,
    expected_previous_goal: str,
    expected_target: str,
    timeout: float = 8,
) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, route_continuation_menu_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        item = state.get("continuationItem") or {}
        if (
            state
            and state.get("error") is None
            and state.get("readyState") == "complete"
            and state.get("used") is True
            and item.get("command") == "continueRouteAssistPath"
            and item.get("routeTarget") == expected_target
            and state.get("scene") == "map"
            and state.get("map") == expected_source
            and state.get("trialTransitions") == "routeAssist"
            and state.get("beforeSelectedRouteGoal") == expected_previous_goal
            and state.get("selectedRouteGoal") == expected_target
            and state.get("routePathValue") == expected_target
            and state.get("transitionTargetParam") == expected_target
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route continuation menu did not become ready: {state!r}")


def wait_for_route_continuation_entry_state(
    port: int,
    session_id: str,
    expected_source: str,
    expected_target: str,
    expected_count: int,
    timeout: float = 8,
) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, route_continuation_entry_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        auto_save = state.get("routeAssistAutoSave") or {}
        counts = ((state.get("progress") or {}).get("counts") or {})
        saved_counts = ((state.get("savedProgress") or {}).get("counts") or {})
        auto_counts = ((auto_save.get("progress") or {}).get("counts") or {})
        if (
            state
            and state.get("error") is None
            and state.get("readyState") == "complete"
            and state.get("entered") is True
            and state.get("scene") == "map"
            and state.get("map") == expected_target
            and state.get("beforeMap") == expected_source
            and state.get("trialTransitions") == "routeAssist"
            and state.get("selectedRouteGoal") == expected_target
            and state.get("routePathValue") == expected_target
            and state.get("routeGoalParam") == expected_target
            and auto_save.get("saved") is True
            and auto_save.get("payloadMap") == expected_target
            and counts.get("route-candidate") == expected_count
            and saved_counts.get("route-candidate") == expected_count
            and auto_counts.get("route-candidate") == expected_count
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route continuation entry did not become ready: {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_route_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_route_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") == "map2_18d"
            and foot.get("x") == 47
            and foot.get("y") == 47
            and state.get("quickLoadHidden") is False
            and state.get("quickLoadText") == "임시 불러오기"
            and has_restored_route_query(str(state.get("search") or ""), "map2_18d", "47,47")
            and state.get("trialTransitions") == "routeAssist"
            and state.get("routePathValue") == "map2_18d"
            and state.get("routeNextText") == "완료"
            and counts.get("route-candidate") == 2
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"title continue did not restore route map: {state!r}")


def wait_for_title_route_completion_gate(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_route_completion_gate_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        click = state.get("click") or {}
        title_gate = state.get("titleGate") or {}
        notice = state.get("notice") or {}
        completion = state.get("completion") or {}
        route_completion = completion.get("route") or {}
        playable_gate = completion.get("playableGate") or {}
        title_payload = state.get("titleRouteCompletionPayload") or {}
        payload_counts = state.get("payloadCounts") or {}
        payload_story_flags = state.get("payloadStoryFlags") or {}
        notice_auto_save = notice.get("autoSave") or {}
        expected_story_key = "story:route-complete:map2_14j:route-complete:map2_14j"
        if (
            state
            and click.get("ok") is True
            and click.get("activated") is True
            and click.get("titleItem", {}).get("key") == "routeCompletion"
            and click.get("titleItem", {}).get("label") == "후보 완료 map2_14j"
            and "routeCompletion" in (click.get("titleMenuKeys") or [])
            and "후보 완료 map2_14j" in (click.get("titleMenuLabels") or [])
            and title_payload.get("goal") == "map2_14j"
            and title_payload.get("routeCompleteCount") == 1
            and payload_counts.get("route-complete") == 1
            and title_gate.get("source") == "title-route-completion-gate"
            and title_gate.get("loaded") is True
            and title_gate.get("opened") is True
            and title_gate.get("map") == "map2_14j"
            and title_gate.get("activeId") == "route-complete:map2_14j"
            and state.get("scene") == "map"
            and state.get("map") == "map2_14j"
            and state.get("activeId") == "route-complete:map2_14j"
            and notice.get("blockId") == "route-complete:map2_14j"
            and notice.get("routeCompleteCount") == 1
            and notice_auto_save.get("source") == "route-complete"
            and expected_story_key in story_flag_keys(notice_auto_save.get("storyFlags") or {})
            and expected_story_key in story_flag_keys(payload_story_flags)
            and notice_auto_save.get("duplicateProgress") is True
            and route_completion.get("routeCompleteCount") == 1
            and route_completion.get("routeCompleteRecorded") is True
            and completion.get("playableGateOpen") is True
            and playable_gate.get("source") == "prototype-playable-route-gate"
            and playable_gate.get("status") == "open"
            and playable_gate.get("opened") is True
            and playable_gate.get("label") == "웹 후보 루트 완료 map2_14j"
            and "웹 후보 루트 완료 게이트가 열렸습니다." in "\n".join(state.get("activeLines") or [])
        ):
            verify_story_flags(
                payload_story_flags,
                {expected_story_key},
                {"route-complete": 1},
                "title route completion gate payload",
            )
            verify_story_flags(
                notice_auto_save.get("storyFlags") or {},
                {expected_story_key},
                {"route-complete": 1},
                "title route completion gate autosave",
            )
            verify_route_gate_feedback(
                state,
                "route-completion-feedback",
                "route-complete",
                "후보 완료 map2_14j",
                0,
            )
            return state
        time.sleep(0.2)
    raise WebDriverError(f"title route completion gate did not open: {state!r}")


def wait_for_title_route_clear_gate(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_route_clear_gate_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        click = state.get("click") or {}
        title_gate = state.get("titleGate") or {}
        summary = state.get("summary") or {}
        completion = state.get("completion") or {}
        route_completion = completion.get("route") or {}
        playable_gate = completion.get("playableGate") or {}
        title_payload = state.get("titleRouteClearPayload") or {}
        payload_counts = state.get("payloadCounts") or {}
        payload_story_flags = state.get("payloadStoryFlags") or {}
        progress_review_lines = "\n".join((state.get("progressReviewBlock") or {}).get("lines") or [])
        summary_progress = summary.get("progressEvent") or {}
        summary_auto_save = summary.get("autoSave") or {}
        saved_progress = (summary_auto_save.get("progress") or {}).get("counts") or {}
        expected_story_keys = {
            "story:route-complete:map2_14j:route-complete:map2_14j",
            "story:route-clear:map2_14j:route-clear:map2_14j",
        }
        menu_commands = state.get("menuCommands") or []
        lines = "\n".join(state.get("activeLines") or [])
        if (
            state
            and click.get("ok") is True
            and click.get("activated") is True
            and click.get("titleItem", {}).get("key") == "routeClear"
            and click.get("titleItem", {}).get("label") == "후보 클리어 저장 map2_14j"
            and "routeClear" in (click.get("titleMenuKeys") or [])
            and "후보 클리어 저장 map2_14j" in (click.get("titleMenuLabels") or [])
            and title_gate.get("source") == "title-route-clear-gate"
            and title_gate.get("loaded") is True
            and title_gate.get("opened") is True
            and title_gate.get("map") == "map2_14j"
            and title_gate.get("activeId") == "route-clear:map2_14j"
            and title_payload.get("goal") == "map2_14j"
            and title_payload.get("routeCompleteCount") == 1
            and title_payload.get("routeClearCount") == 1
            and state.get("scene") == "map"
            and state.get("map") == "map2_14j"
            and state.get("activeId") == "route-clear:map2_14j"
            and summary.get("blockId") == "route-clear:map2_14j"
            and summary.get("routeClearCount") == 1
            and summary_progress.get("kind") == "route-clear"
            and summary_progress.get("id") == "route-clear:map2_14j"
            and summary_auto_save.get("saved") is True
            and summary_auto_save.get("source") == "route-clear"
            and summary_auto_save.get("payloadMap") == "map2_14j"
            and expected_story_keys.issubset(story_flag_keys(summary_auto_save.get("storyFlags") or {}))
            and expected_story_keys.issubset(story_flag_keys(payload_story_flags))
            and summary_auto_save.get("duplicateProgress") is False
            and payload_counts.get("route-complete") == 1
            and payload_counts.get("route-clear") == 1
            and saved_progress.get("route-complete") == 1
            and saved_progress.get("route-clear") == 1
            and route_completion.get("routeCompleteCount") == 1
            and route_completion.get("routeClearCount") == 1
            and route_completion.get("routeClearRecorded") is True
            and completion.get("playableGateOpen") is True
            and playable_gate.get("source") == "prototype-playable-route-gate"
            and playable_gate.get("status") == "open"
            and playable_gate.get("opened") is True
            and any(
                command.get("command") == "showRouteClearSummary"
                and command.get("name") == "후보 클리어 map2_14j"
                and command.get("usable") is True
                for command in menu_commands
            )
            and "후보 클리어 요약 map2_14j" in lines
            and "진행 기록 9" in lines
            and "결과 소지금 80 / 보상 약초 2" in lines
            and "웹 후보 클리어 저장이 고정되었습니다." in lines
            and "원본 full-game ending/story flag 완료는 아직 아닙니다." in lines
            and "후보 클리어 1" in progress_review_lines
            and "route-clear:map2_14j" in progress_review_lines
            and "클리어 목표 map2_14j" in progress_review_lines
            and "클리어 진행 기록 9" in progress_review_lines
            and "클리어 결과 소지금 80 / 보상 약초 2" in progress_review_lines
            and "클리어 map2_14j" in str(state.get("titleContinueLabel") or "")
        ):
            verify_story_flags(
                payload_story_flags,
                expected_story_keys,
                {"route-complete": 1, "route-clear": 1},
                "title route clear gate payload",
            )
            verify_story_flags(
                summary_auto_save.get("storyFlags") or {},
                expected_story_keys,
                {"route-complete": 1, "route-clear": 1},
                "title route clear gate autosave",
            )
            verify_route_gate_feedback(
                state,
                "route-clear-feedback",
                "route-clear",
                "후보 클리어 map2_14j",
                1,
            )
            return state
        time.sleep(0.2)
    raise WebDriverError(f"title route clear gate did not open: {state!r}")


def wait_for_title_continue_route_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_route_restore_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        counts = ((state.get("progress") or {}).get("counts") or {})
        if (
            state
            and state.get("error") is None
            and state.get("titleContinue") is True
            and state.get("map") == "map2_18d"
            and state.get("activeId") == "prototype-progress:map2_18d"
            and counts.get("route-candidate") == 2
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route title continue restore did not become ready: {state!r}")


def wait_for_route_continuation_title_restore_state(
    port: int,
    session_id: str,
    expected_map: str,
    expected_next_target: str | None,
    expected_count: int | None = None,
    timeout: float = 8,
) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, route_continuation_title_restore_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        continuation_item = state.get("continuationItem") or {}
        counts = ((state.get("progress") or {}).get("counts") or {})
        saved_counts = ((state.get("savedProgress") or {}).get("counts") or {})
        if (
            state
            and state.get("error") is None
            and state.get("readyState") == "complete"
            and state.get("titleContinue") is True
            and state.get("scene") == "map"
            and state.get("map") == expected_map
            and state.get("trialTransitions") == "routeAssist"
            and state.get("selectedRouteGoal") == expected_map
            and state.get("routePathValue") == expected_map
            and (expected_next_target is None or continuation_item.get("routeTarget") == expected_next_target)
            and (expected_count is None or counts.get("route-candidate") == expected_count)
            and (expected_count is None or saved_counts.get("route-candidate") == expected_count)
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route continuation title restore did not become ready: {state!r}")


def wait_for_route_next_continuation_button_state(
    port: int,
    session_id: str,
    expected_source: str,
    expected_target: str,
    timeout: float = 8,
) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, route_next_continuation_button_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        if (
            state
            and state.get("error") is None
            and state.get("readyState") == "complete"
            and state.get("scene") == "map"
            and state.get("map") == expected_source
            and state.get("activated") is True
            and state.get("trialTransitions") == "routeAssist"
            and state.get("selectedRouteGoal") == expected_target
            and state.get("routePathValue") == expected_target
            and state.get("transitionTargetParam") == expected_target
            and state.get("routeNextText") == "진입"
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate routeNext continuation button did not become ready: {state!r}")


def wait_for_route_objective_action_state(
    port: int,
    session_id: str,
    storage_key: str,
    expected_action: str,
    expected_map: str,
    timeout: float = 8,
) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, route_objective_action_state_script(storage_key), timeout=3)
        state = value if isinstance(value, dict) else {}
        objective_action = state.get("objectiveAction") or {}
        if (
            state
            and state.get("error") is None
            and state.get("readyState") == "complete"
            and state.get("scene") == "map"
            and state.get("map") == expected_map
            and state.get("objectiveActionResult") is True
            and objective_action.get("action") == expected_action
            and objective_action.get("handled") is True
            and (
                expected_action != "route-continuation"
                or (
                    state.get("routeGoalParam") == "map2_09g"
                    and state.get("transitionTargetParam") == "map2_09g"
                    and state.get("routeAutoSaveParam") == "1"
                    and state.get("routeNextText") == "진입"
                )
            )
            and (
                expected_action != "field-encounter-enable"
                or (state.get("fieldEncounter") or {}).get("enabled") is True
            )
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route objective action did not become ready: {state!r}")


def wait_for_quick_objective_control_state(
    port: int,
    session_id: str,
    storage_key: str,
    expected_source: str,
    expected_action: str,
    expected_map: str,
    timeout: float = 8,
) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, quick_objective_control_state_script(storage_key), timeout=3)
        state = value if isinstance(value, dict) else {}
        quick_action = state.get("quickAction") or {}
        if (
            state
            and state.get("error") is None
            and state.get("readyState") == "complete"
            and state.get("scene") == "map"
            and state.get("map") == expected_map
            and quick_action.get("source") == expected_source
            and quick_action.get("action") == expected_action
            and quick_action.get("handled") is True
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"quick objective control did not become ready: {state!r}")


def wait_for_route_continuation_field_encounter(
    port: int,
    session_id: str,
    expected_map: str,
    expected_count: int,
    timeout: float = 10,
) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, route_continuation_field_encounter_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        field = state.get("fieldEncounter") or {}
        route = state.get("routeState") or {}
        progress = state.get("progress") or {}
        counts = progress.get("counts") or {}
        saved_payload = state.get("savedPayload") or {}
        saved_tile = saved_payload.get("tile") or {}
        saved_route = saved_payload.get("routeState") or {}
        saved_field = saved_payload.get("fieldEncounter") or {}
        auto_save = state.get("victoryAutoSave") or {}
        auto_save_tile = auto_save.get("payloadTile") or {}
        auto_save_route = auto_save.get("routeState") or {}
        auto_save_field = auto_save.get("fieldEncounter") or {}
        movement = state.get("movement") or {}
        before_foot = movement.get("beforeFoot") or {}
        after_foot = movement.get("afterFoot") or {}
        completion = state.get("completion") or {}
        route_completion = completion.get("route") or {}
        completion_field = route_completion.get("fieldEncounter") or {}
        play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
        if (
            state
            and state.get("error") is None
            and state.get("pending") is False
            and state.get("movementInput") is True
            and state.get("inputCode")
            and movement.get("tileChanged") is True
            and before_foot.get("x") is not None
            and before_foot.get("y") is not None
            and after_foot.get("x") is not None
            and after_foot.get("y") is not None
            and state.get("started") is True
            and state.get("scene") == "map"
            and state.get("mapName") == expected_map
            and (state.get("foot") or {}).get("x") == after_foot.get("x")
            and (state.get("foot") or {}).get("y") == after_foot.get("y")
            and state.get("victoryResult") is True
            and state.get("closeResult") is True
            and route.get("trialTransitions") == "routeAssist"
            and route.get("selectedRouteGoal") == expected_map
            and field.get("enabled") is True
            and field.get("stepCount") == 0
            and field.get("lastMap") == expected_map
            and counts.get("route-candidate") == expected_count
            and counts.get("battle-start") == 1
            and counts.get("field-encounter") == 1
            and counts.get("field-encounter-victory") == 1
            and counts.get("battle-victory", 0) == 0
            and auto_save.get("saved") is True
            and auto_save.get("source") == "field-encounter-victory"
            and auto_save.get("payloadMap") == expected_map
            and auto_save_tile.get("x") == after_foot.get("x")
            and auto_save_tile.get("y") == after_foot.get("y")
            and auto_save_route.get("trialTransitions") == "routeAssist"
            and auto_save_route.get("selectedRouteGoal") == expected_map
            and auto_save_field.get("enabled") is True
            and auto_save_field.get("stepCount") == 0
            and auto_save_field.get("lastMap") == expected_map
            and saved_payload.get("map") == expected_map
            and saved_tile.get("x") == after_foot.get("x")
            and saved_tile.get("y") == after_foot.get("y")
            and saved_route.get("trialTransitions") == "routeAssist"
            and saved_route.get("selectedRouteGoal") == expected_map
            and saved_field.get("enabled") is True
            and saved_field.get("stepCount") == 0
            and saved_field.get("lastMap") == expected_map
            and completion.get("completed") is True
            and route_completion.get("source") == "prototype-route-assist-completion"
            and route_completion.get("completed") is True
            and route_completion.get("currentMap") == expected_map
            and route_completion.get("selectedRouteGoal") == expected_map
            and route_completion.get("trialTransitions") == "routeAssist"
            and route_completion.get("routeCandidateCount") == expected_count
            and route_completion.get("fieldEncounterCount") == 1
            and route_completion.get("fieldEncounterVictoryCount") == 1
            and completion_field.get("enabled") is True
            and completion_field.get("stepCount") == 0
            and completion_field.get("lastMap") == expected_map
            and route_completion.get("originalRoutePromotionImplemented") is False
            and route_completion.get("originalEncounterRuntimeImplemented") is False
            and f"완료 {expected_map}" in play_hud
            and state.get("quickLoadHidden") is False
            and state.get("quickLoadText") == "임시 불러오기"
            and state.get("originalRoutePromotionImplemented") is False
            and state.get("originalEncounterRuntimeImplemented") is False
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate route continuation field encounter did not finish/save: {state!r}")


def wait_for_route_completion_notice(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, route_completion_notice_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        notice = state.get("notice") or {}
        completion = (state.get("completion") or {}).get("route") or {}
        before = state.get("before") or {}
        lines = "\n".join(state.get("activeLines") or [])
        menu_lines = "\n".join(state.get("menuActiveLines") or [])
        progress_review_lines = "\n".join((state.get("progressReviewBlock") or {}).get("lines") or [])
        hud_lines = "\n".join((state.get("playHudLines") or []) + (before.get("playHudLines") or []))
        notice_progress = notice.get("progressEvent") or {}
        notice_auto_save = notice.get("autoSave") or {}
        saved_progress = (notice_auto_save.get("progress") or {}).get("counts") or {}
        notice_gate = notice.get("playableGate") or {}
        state_gate = (state.get("completion") or {}).get("playableGate") or {}
        menu_commands = state.get("menuCommands") or []
        if (
            state
            and state.get("error") is None
            and state.get("activeId") == "route-complete:map2_14j"
            and notice.get("blockId") == "route-complete:map2_14j"
            and before.get("routeNextText") == "완료"
            and before.get("routeCompleted") == "1"
            and state.get("routeNextText") == "완료"
            and completion.get("completed") is True
            and completion.get("source") == "prototype-route-assist-completion"
            and completion.get("currentMap") == "map2_14j"
            and completion.get("selectedRouteGoal") == "map2_14j"
            and completion.get("routeCandidateCount") == 4
            and completion.get("fieldEncounterVictoryCount") == 1
            and completion.get("routeCompleteCount") == 1
            and completion.get("routeCompleteRecorded") is True
            and notice.get("routeCompleteCount") == 1
            and notice_progress.get("kind") == "route-complete"
            and notice_progress.get("id") == "route-complete:map2_14j"
            and notice_auto_save.get("saved") is True
            and notice_auto_save.get("source") == "route-complete"
            and notice_auto_save.get("payloadMap") == "map2_14j"
            and notice_auto_save.get("duplicateProgress") is True
            and saved_progress.get("route-complete") == 1
            and notice_gate.get("source") == "prototype-playable-route-gate"
            and notice_gate.get("id") == "prototype-route-gate:map2_14j"
            and notice_gate.get("status") == "open"
            and notice_gate.get("opened") is True
            and notice_gate.get("canFinishPrototype") is True
            and notice_gate.get("label") == "웹 후보 루트 완료 map2_14j"
            and notice_gate.get("routeCompleteCount") == 1
            and notice_gate.get("originalFullGameCompletionImplemented") is False
            and state_gate.get("source") == "prototype-playable-route-gate"
            and state_gate.get("status") == "open"
            and state_gate.get("opened") is True
            and state_gate.get("canFinishPrototype") is True
            and "목표 후보 루트 완료 map2_14j" in hud_lines
            and "다음 완료 버튼으로 결과 확인" in hud_lines
            and state.get("objectiveActionResult") is True
            and state.get("objectiveActiveId") == "route-complete:map2_14j"
            and (state.get("objectiveAction") or {}).get("action") == "route-completion-notice"
            and (state.get("objectiveAction") or {}).get("handled") is True
            and state.get("menuCompletionResult") is True
            and state.get("menuActiveId") == "route-complete:map2_14j"
            and any(
                command.get("command") == "showRouteCompletionNotice"
                and command.get("name") == "후보 완료 map2_14j"
                and command.get("usable") is True
                for command in menu_commands
            )
            and "후보 루트 완료 map2_14j" in lines
            and "후보 루트 완료 map2_14j" in menu_lines
            and "웹 후보 루트 완료 게이트가 열렸습니다." in lines
            and "후보 완료 1" in progress_review_lines
            and "route-complete:map2_14j" in progress_review_lines
            and "원본 normal-route/story flag 실행 증명은 아직 아닙니다." in lines
            and notice.get("originalRoutePromotionImplemented") is False
            and notice.get("originalEncounterRuntimeImplemented") is False
            and notice.get("originalStoryFlagRuntimeImplemented") is False
        ):
            verify_route_gate_feedback(
                state,
                "route-completion-feedback",
                "route-complete",
                "후보 완료 map2_14j",
                0,
            )
            return state
        time.sleep(0.2)
    raise WebDriverError(f"route completion notice did not open: {state!r}")


def wait_for_restored_route_completion_menu_notice(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, restored_route_completion_menu_notice_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        notice = state.get("notice") or {}
        completion = state.get("completion") or {}
        route_completion = completion.get("route") or {}
        playable_gate = completion.get("playableGate") or {}
        notice_progress = notice.get("progressEvent") or {}
        notice_auto_save = notice.get("autoSave") or {}
        saved_progress = (notice_auto_save.get("progress") or {}).get("counts") or {}
        progress_review_lines = "\n".join((state.get("progressReviewBlock") or {}).get("lines") or [])
        lines = "\n".join(state.get("activeLines") or [])
        menu_commands = state.get("menuCommands") or []
        if (
            state
            and state.get("error") is None
            and state.get("result") is True
            and state.get("activeId") == "route-complete:map2_14j"
            and notice.get("blockId") == "route-complete:map2_14j"
            and notice.get("routeCompleteCount") == 1
            and notice_progress.get("kind") == "route-complete"
            and notice_progress.get("id") == "route-complete:map2_14j"
            and notice_auto_save.get("saved") is True
            and notice_auto_save.get("source") == "route-complete"
            and notice_auto_save.get("duplicateProgress") is True
            and saved_progress.get("route-complete") == 1
            and completion.get("playableGateOpen") is True
            and route_completion.get("routeCompleteCount") == 1
            and route_completion.get("routeCompleteRecorded") is True
            and playable_gate.get("source") == "prototype-playable-route-gate"
            and playable_gate.get("status") == "open"
            and playable_gate.get("opened") is True
            and playable_gate.get("label") == "웹 후보 루트 완료 map2_14j"
            and any(
                command.get("command") == "showRouteCompletionNotice"
                and command.get("name") == "후보 완료 map2_14j"
                and command.get("usable") is True
                for command in menu_commands
            )
            and "후보 루트 완료 map2_14j" in lines
            and "웹 후보 루트 완료 게이트가 열렸습니다." in lines
            and "후보 완료 1" in progress_review_lines
            and "route-complete:map2_14j" in progress_review_lines
        ):
            verify_route_gate_feedback(
                state,
                "route-completion-feedback",
                "route-complete",
                "후보 완료 map2_14j",
                0,
            )
            return state
        time.sleep(0.2)
    raise WebDriverError(f"restored route completion menu notice did not open: {state!r}")


def wait_for_restored_route_clear_menu_summary(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, restored_route_clear_menu_summary_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        summary = state.get("summary") or {}
        completion = state.get("completion") or {}
        route_completion = completion.get("route") or {}
        playable_gate = completion.get("playableGate") or {}
        summary_progress = summary.get("progressEvent") or {}
        summary_auto_save = summary.get("autoSave") or {}
        saved_progress = (summary_auto_save.get("progress") or {}).get("counts") or {}
        progress_review_lines = "\n".join((state.get("progressReviewBlock") or {}).get("lines") or [])
        lines = "\n".join(state.get("activeLines") or [])
        menu_commands = state.get("menuCommands") or []
        if (
            state
            and state.get("error") is None
            and state.get("result") is True
            and state.get("activeId") == "route-clear:map2_14j"
            and summary.get("blockId") == "route-clear:map2_14j"
            and summary.get("routeClearCount") == 1
            and summary_progress.get("kind") == "route-clear"
            and summary_progress.get("id") == "route-clear:map2_14j"
            and summary_auto_save.get("saved") is True
            and summary_auto_save.get("source") == "route-clear"
            and summary_auto_save.get("duplicateProgress") is True
            and saved_progress.get("route-complete") == 1
            and saved_progress.get("route-clear") == 1
            and completion.get("playableGateOpen") is True
            and route_completion.get("routeCompleteCount") == 1
            and route_completion.get("routeClearCount") == 1
            and route_completion.get("routeClearRecorded") is True
            and playable_gate.get("source") == "prototype-playable-route-gate"
            and playable_gate.get("status") == "open"
            and playable_gate.get("opened") is True
            and any(
                command.get("command") == "showRouteClearSummary"
                and command.get("name") == "후보 클리어 map2_14j"
                and command.get("usable") is True
                for command in menu_commands
            )
            and "후보 클리어 요약 map2_14j" in lines
            and "진행 기록 9" in lines
            and "결과 소지금 80 / 보상 약초 2" in lines
            and "웹 후보 클리어 저장이 고정되었습니다." in lines
            and "후보 클리어 1" in progress_review_lines
            and "route-clear:map2_14j" in progress_review_lines
            and "클리어 목표 map2_14j" in progress_review_lines
            and "클리어 진행 기록 9" in progress_review_lines
            and "클리어 결과 소지금 80 / 보상 약초 2" in progress_review_lines
        ):
            verify_route_gate_feedback(
                state,
                "route-clear-feedback",
                "route-clear",
                "후보 클리어 map2_14j",
                1,
            )
            return state
        time.sleep(0.2)
    raise WebDriverError(f"restored route clear menu summary did not open: {state!r}")


def wait_for_route_clear_controls(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, route_clear_controls_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        before = state.get("before") or {}
        objective = before.get("objective") or {}
        objective_action = state.get("objectiveAction") or {}
        objective_summary = state.get("objectiveSummary") or {}
        button_summary = state.get("buttonSummary") or {}
        completion = state.get("completion") or {}
        route_completion = completion.get("route") or {}
        progress_review_lines = "\n".join((state.get("progressReviewBlock") or {}).get("lines") or [])
        play_hud = "\n".join((state.get("playHudLines") or []) + (before.get("playHudLines") or []))
        objective_auto_save = objective_summary.get("autoSave") or {}
        button_auto_save = button_summary.get("autoSave") or {}
        objective_saved = (objective_auto_save.get("progress") or {}).get("counts") or {}
        button_saved = (button_auto_save.get("progress") or {}).get("counts") or {}
        objective_lines = "\n".join(state.get("objectiveActiveLines") or [])
        button_lines = "\n".join(state.get("buttonActiveLines") or [])
        if (
            state
            and state.get("error") is None
            and before.get("routeNextText") == "클리어"
            and before.get("routeCompleted") == "1"
            and before.get("routeCleared") == "1"
            and objective.get("title") == "후보 루트 클리어 map2_14j"
            and objective.get("nextAction") == "클리어 요약 확인"
            and state.get("objectiveResult") is True
            and objective_action.get("action") == "route-clear-summary"
            and objective_action.get("handled") is True
            and state.get("objectiveActiveId") == "route-clear:map2_14j"
            and objective_summary.get("blockId") == "route-clear:map2_14j"
            and (objective_summary.get("progressEvent") or {}).get("kind") == "route-clear"
            and objective_auto_save.get("source") == "route-clear"
            and objective_auto_save.get("duplicateProgress") is True
            and objective_saved.get("route-clear") == 1
            and objective_saved.get("route-complete") == 1
            and state.get("buttonResult") is True
            and state.get("buttonActiveId") == "route-clear:map2_14j"
            and button_summary.get("blockId") == "route-clear:map2_14j"
            and (button_summary.get("progressEvent") or {}).get("kind") == "route-clear"
            and button_auto_save.get("source") == "route-clear"
            and button_auto_save.get("duplicateProgress") is True
            and button_saved.get("route-clear") == 1
            and button_saved.get("route-complete") == 1
            and route_completion.get("routeClearCount") == 1
            and route_completion.get("routeClearRecorded") is True
            and "후보 클리어 요약 map2_14j" in objective_lines
            and "진행 기록 9" in objective_lines
            and "결과 소지금 80 / 보상 약초 2" in objective_lines
            and "후보 클리어 요약 map2_14j" in button_lines
            and "진행 기록 9" in button_lines
            and "결과 소지금 80 / 보상 약초 2" in button_lines
            and "목표 후보 루트 클리어 map2_14j" in play_hud
            and "다음 클리어 요약 확인" in play_hud
            and "후보 클리어 1" in progress_review_lines
            and "route-clear:map2_14j" in progress_review_lines
            and "클리어 목표 map2_14j" in progress_review_lines
            and "클리어 진행 기록 9" in progress_review_lines
            and "클리어 결과 소지금 80 / 보상 약초 2" in progress_review_lines
        ):
            verify_route_gate_feedback(
                state,
                "route-clear-feedback",
                "route-clear",
                "후보 클리어 map2_14j",
                1,
            )
            return state
        time.sleep(0.2)
    raise WebDriverError(f"route clear controls did not open clear summary: {state!r}")


def verify_route_candidate_auto_save(
    auto_save: dict,
    expected_source: str,
    expected_target: str,
    expected_count: int,
    expected_activation_tile: dict,
    expected_payload_tile: dict,
    expected_block_reasons: list[str] | None = None,
) -> None:
    progress = auto_save.get("progress") or {}
    counts = progress.get("counts") or {}
    progress_event = auto_save.get("progressEvent") or {}
    detail = progress_event.get("detail") or {}
    expected_id = f"{expected_source}->{expected_target}"
    if (
        auto_save.get("saved") is not True
        or auto_save.get("source") != "route-candidate"
        or auto_save.get("map") != expected_target
        or auto_save.get("payloadMap") != expected_target
        or (auto_save.get("tile") or {}) != expected_activation_tile
        or (auto_save.get("payloadTile") or {}) != expected_payload_tile
        or auto_save.get("id") != expected_id
        or auto_save.get("sourceMap") != expected_source
        or auto_save.get("targetMap") != expected_target
        or counts.get("route-candidate") != expected_count
        or progress_event.get("kind") != "route-candidate"
        or progress_event.get("id") != expected_id
        or progress_event.get("map") != expected_source
        or detail.get("sourceMap") != expected_source
        or detail.get("targetMap") != expected_target
        or detail.get("originalRoutePromotionImplemented") is not False
        or auto_save.get("originalRoutePromotionImplemented") is not False
        or auto_save.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate route auto-save is incomplete: {auto_save!r}")
    if expected_block_reasons is not None:
        verify_trial_blocker_fields(auto_save, expected_block_reasons, "route candidate auto-save")
        verify_trial_blocker_fields(detail, expected_block_reasons, "route candidate auto-save detail")


def verify_route_menu_start_state(state: dict) -> None:
    start_item = state.get("startItem") or {}
    next_item = state.get("nextItem") or {}
    before_labels = state.get("beforeLabels") or []
    after_labels = state.get("afterLabels") or []
    if (
        state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or (state.get("tile") or {}).get("x") != 11
        or (state.get("tile") or {}).get("y") != 12
        or state.get("used") is not True
        or state.get("trialTransitions") != "routeAssist"
        or state.get("selectedRouteGoal") != "map2_02d"
        or "trialTransitions=routeAssist" not in str(state.get("search") or "")
        or "routeGoal=map2_02d" not in str(state.get("search") or "")
        or state.get("routePathValue") != "map2_02d"
        or start_item.get("command") != "startRouteAssistPath"
        or start_item.get("routeTarget") != "map2_02d"
        or next_item.get("command") != "activateNextRoutePath"
        or next_item.get("routeTarget") != "map2_02d"
        or next_item.get("routeNextTarget") != "map1_01a"
        or "후보 진행 map2_02d" not in before_labels
        or "후보 다음 map1_01a" not in after_labels
        or not any("후보 map2_02d" in str(line) and "다음 map1_01a" in str(line) for line in state.get("playHudLines") or [])
        or "map1_02b -> map1_01a" not in str(state.get("routeNextTitle") or "")
    ):
        raise WebDriverError(f"candidate route menu start is incomplete: {state!r}")


def verify_route_goal_menu_state(state: dict) -> None:
    goal_menu_item = state.get("goalMenuItem") or {}
    target_item = state.get("targetItem") or {}
    next_item = state.get("nextItem") or {}
    main_labels = state.get("mainLabels") or []
    goal_labels = state.get("goalLabels") or []
    after_labels = state.get("afterLabels") or []
    if (
        state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or (state.get("tile") or {}).get("x") != 11
        or (state.get("tile") or {}).get("y") != 12
        or state.get("opened") is not True
        or state.get("selected") is not True
        or state.get("trialTransitions") != "routeAssist"
        or state.get("selectedRouteGoal") != "map2_18d"
        or state.get("menuMode") != "main"
        or "trialTransitions=routeAssist" not in str(state.get("search") or "")
        or "routeGoal=map2_18d" not in str(state.get("search") or "")
        or state.get("routePathValue") != "map2_18d"
        or goal_menu_item.get("command") != "openRouteGoalMenu"
        or "후보 목표 25" not in main_labels
        or target_item.get("command") != "selectRouteAssistGoal"
        or target_item.get("routeTarget") != "map2_18d"
        or "map2_18d" not in str(target_item.get("name") or "")
        or not any("map2_18d" in str(label) for label in goal_labels)
        or next_item.get("command") != "activateNextRoutePath"
        or next_item.get("routeTarget") != "map2_18d"
        or next_item.get("routeNextTarget") != "map1_01a"
        or "후보 다음 map1_01a" not in after_labels
        or "후보 목표: map2_18d" not in str(state.get("menuNotice") or "")
        or not any("후보 map2_18d" in str(line) and "다음 map1_01a" in str(line) for line in state.get("playHudLines") or [])
        or "map1_02b -> map1_01a" not in str(state.get("routeNextTitle") or "")
    ):
        raise WebDriverError(f"candidate route goal menu is incomplete: {state!r}")


def verify_route_continuation_menu_state(
    state: dict,
    expected_source: str,
    expected_previous_goal: str,
    expected_target: str,
) -> None:
    continuation_item = state.get("continuationItem") or {}
    before_labels = state.get("beforeLabels") or []
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    before_hud = " ".join(str(line) for line in state.get("beforeHudLines") or [])
    expected_label = f"후보 이어가기 {expected_target}"
    expected_edge = f"{expected_source} -> {expected_target}"
    if (
        state.get("scene") != "map"
        or state.get("map") != expected_source
        or state.get("used") is not True
        or state.get("continuationIndex", -1) < 0
        or continuation_item.get("command") != "continueRouteAssistPath"
        or continuation_item.get("name") != expected_label
        or continuation_item.get("routeTarget") != expected_target
        or continuation_item.get("routeNextTarget") != expected_target
        or state.get("beforeMap") != expected_source
        or state.get("beforeTrialTransitions") != "routeAssist"
        or state.get("beforeSelectedRouteGoal") != expected_previous_goal
        or state.get("beforeRoutePathValue") != expected_previous_goal
        or state.get("beforeRouteNextText") != "이어가기"
        or state.get("beforeRouteContinuationTarget") != expected_target
        or expected_edge not in str(state.get("beforeRouteNextTitle") or "")
        or expected_label not in before_labels
        or f"다음 목표 {expected_target}" not in before_hud
        or state.get("trialTransitions") != "routeAssist"
        or state.get("selectedRouteGoal") != expected_target
        or state.get("routePathValue") != expected_target
        or state.get("transitionTargetParam") != expected_target
        or state.get("routeGoalParam") != expected_target
        or state.get("routeAutoSaveParam") != "1"
        or "trialTransitions=routeAssist" not in str(state.get("search") or "")
        or f"transitionTarget={expected_target}" not in str(state.get("search") or "")
        or f"routeGoal={expected_target}" not in str(state.get("search") or "")
        or state.get("routeNextText") != "진입"
        or expected_edge not in str(state.get("routeNextTitle") or "")
        or f"후보 {expected_target}" not in play_hud
        or f"다음 {expected_target}" not in play_hud
    ):
        raise WebDriverError(f"candidate route continuation menu is incomplete: {state!r}")


def verify_route_next_continuation_button_state(
    state: dict,
    expected_source: str,
    expected_previous_goal: str,
    expected_target: str,
) -> None:
    route_action = state.get("routeControlAction") or {}
    route_sound = route_action.get("sound") or state.get("routeControlSound") or state.get("lastRouteControlSound") or {}
    sound_log = state.get("routeControlSoundLog") or []
    last_sound = state.get("lastSound") or {}
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    before_hud = " ".join(str(line) for line in state.get("beforeHudLines") or [])
    expected_edge = f"{expected_source} -> {expected_target}"
    if (
        state.get("scene") != "map"
        or state.get("map") != expected_source
        or state.get("activated") is not True
        or state.get("beforeMap") != expected_source
        or state.get("beforeTrialTransitions") != "routeAssist"
        or state.get("beforeSelectedRouteGoal") != expected_previous_goal
        or state.get("beforeRoutePathValue") != expected_previous_goal
        or state.get("beforeRouteNextText") != "이어가기"
        or state.get("beforeRouteContinuationTarget") != expected_target
        or expected_edge not in str(state.get("beforeRouteNextTitle") or "")
        or f"다음 목표 {expected_target}" not in before_hud
        or state.get("trialTransitions") != "routeAssist"
        or state.get("selectedRouteGoal") != expected_target
        or state.get("routePathValue") != expected_target
        or state.get("transitionTargetParam") != expected_target
        or state.get("routeGoalParam") != expected_target
        or state.get("routeAutoSaveParam") != "1"
        or "trialTransitions=routeAssist" not in str(state.get("search") or "")
        or f"transitionTarget={expected_target}" not in str(state.get("search") or "")
        or f"routeGoal={expected_target}" not in str(state.get("search") or "")
        or state.get("routeNextText") != "진입"
        or expected_edge not in str(state.get("routeNextTitle") or "")
        or f"후보 {expected_target}" not in play_hud
        or f"다음 {expected_target}" not in play_hud
        or route_action.get("source") != "route-control"
        or route_action.get("action") != "route-continuation"
        or route_action.get("handled") is not True
        or route_action.get("routeTarget") != expected_target
        or route_action.get("nextTarget") != expected_target
        or route_action.get("routeGoal") != expected_target
        or route_action.get("originalRoutePromotionImplemented") is not False
        or route_action.get("originalStoryFlagRuntimeImplemented") is not False
        or route_sound.get("source") != "route-control-sound"
        or route_sound.get("inputSource") != "route-next-button"
        or route_sound.get("action") != "route-continuation"
        or route_sound.get("routeTarget") != expected_target
        or route_sound.get("nextTarget") != expected_target
        or route_sound.get("routeGoal") != expected_target
        or route_sound.get("soundKey") != "menuConfirm"
        or route_sound.get("soundSrc") != "../extract_wlk/04.wav"
        or route_sound.get("soundPlayed") is not True
        or route_sound.get("browserRouteControlSoundImplemented") is not True
        or route_sound.get("originalDirectSoundTimingImplemented") is not False
        or route_sound.get("originalRoutePromotionImplemented") is not False
        or route_sound.get("originalStoryFlagRuntimeImplemented") is not False
        or state.get("menuConfirmSoundCount") not in (0, 1)
        or state.get("routeControlSoundLogLength") not in (0, 1)
        or (sound_log and (sound_log[-1] or {}).get("source") != "route-control-sound")
        or (sound_log and (sound_log[-1] or {}).get("inputSource") != "route-next-button")
        or (sound_log and (sound_log[-1] or {}).get("action") != "route-continuation")
        or (state.get("menuConfirmSoundCount") == 1 and last_sound.get("key") != "menuConfirm")
        or (state.get("menuConfirmSoundCount") == 1 and last_sound.get("src") != "../extract_wlk/04.wav")
    ):
        raise WebDriverError(f"candidate routeNext continuation button is incomplete: {state!r}")


def verify_route_next_objective_action_state(state: dict) -> None:
    objective_action = state.get("objectiveAction") or {}
    objective = objective_action.get("objective") or {}
    labels = state.get("labels") or []
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    before_hud = " ".join(str(line) for line in state.get("beforeHudLines") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map1_01a"
        or state.get("objectiveActionResult") is not True
        or objective_action.get("action") != "route-next"
        or objective_action.get("handled") is not True
        or objective_action.get("routeTarget") != "map2_02d"
        or objective_action.get("originalRoutePromotionImplemented") is not False
        or objective_action.get("originalStoryFlagRuntimeImplemented") is not False
        or objective.get("title") != "후보 목표 map2_02d"
        or "map1_01a" not in str(objective.get("nextAction") or "")
        or state.get("beforeMap") != "map1_02b"
        or state.get("beforeSelectedRouteGoal") != "map2_02d"
        or state.get("beforeRoutePathValue") != "map2_02d"
        or "map1_02b -> map1_01a" not in str(state.get("beforeRouteNextTitle") or "")
        or "후보 map2_02d" not in before_hud
        or "다음 map1_01a" not in before_hud
        or state.get("trialTransitions") != "routeAssist"
        or state.get("selectedRouteGoal") != "map2_02d"
        or state.get("routePathValue") != "map2_02d"
        or state.get("routeGoalParam") != "map2_02d"
        or state.get("routeAutoSaveParam") != "1"
        or state.get("routeNextText") != "후보"
        or "map1_01a -> map2_02d" not in str(state.get("routeNextTitle") or "")
        or "후보 다음 map2_02d" not in labels
        or state.get("savedPayloadMap") != "map1_01a"
        or "trialTransitions=routeAssist" not in str(state.get("search") or "")
        or "routeGoal=map2_02d" not in str(state.get("search") or "")
        or "후보 map2_02d" not in play_hud
    ):
        raise WebDriverError(f"candidate route-next objective action is incomplete: {state!r}")


def verify_route_continuation_objective_action_state(state: dict) -> None:
    objective_action = state.get("objectiveAction") or {}
    objective = objective_action.get("objective") or {}
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    before_hud = " ".join(str(line) for line in state.get("beforeHudLines") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map2_02d"
        or state.get("objectiveActionResult") is not True
        or objective_action.get("action") != "route-continuation"
        or objective_action.get("handled") is not True
        or objective_action.get("routeTarget") != "map2_02d"
        or objective_action.get("originalRoutePromotionImplemented") is not False
        or objective_action.get("originalStoryFlagRuntimeImplemented") is not False
        or objective.get("title") != "후보 목표 map2_02d"
        or "map2_09g" not in str(objective.get("nextAction") or "")
        or state.get("beforeMap") != "map2_02d"
        or state.get("beforeSelectedRouteGoal") != "map2_02d"
        or state.get("beforeRoutePathValue") != "map2_02d"
        or state.get("beforeRouteNextText") != "이어가기"
        or state.get("beforeRouteContinuationTarget") != "map2_09g"
        or "다음 목표 map2_09g" not in before_hud
        or state.get("trialTransitions") != "routeAssist"
        or state.get("selectedRouteGoal") != "map2_09g"
        or state.get("routePathValue") != "map2_09g"
        or state.get("transitionTargetParam") != "map2_09g"
        or state.get("routeGoalParam") != "map2_09g"
        or state.get("routeAutoSaveParam") != "1"
        or state.get("routeNextText") != "진입"
        or "map2_02d -> map2_09g" not in str(state.get("routeNextTitle") or "")
        or "후보 map2_09g" not in play_hud
        or "다음 map2_09g" not in play_hud
    ):
        raise WebDriverError(f"candidate route-continuation objective action is incomplete: {state!r}")


def verify_route_field_encounter_objective_action_state(state: dict) -> None:
    objective_action = state.get("objectiveAction") or {}
    objective = objective_action.get("objective") or {}
    field = state.get("fieldEncounter") or {}
    mode = state.get("fieldEncounterMode") or {}
    mode_auto_save = state.get("fieldEncounterModeAutoSave") or {}
    saved_field = state.get("savedFieldEncounter") or {}
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map2_14j"
        or state.get("objectiveActionResult") is not True
        or objective_action.get("action") != "field-encounter-enable"
        or objective_action.get("handled") is not True
        or objective_action.get("routeTarget") != "map2_14j"
        or objective_action.get("originalEncounterRuntimeImplemented") is not False
        or objective_action.get("originalStoryFlagRuntimeImplemented") is not False
        or objective.get("title") != "후보 목표 map2_14j"
        or "전투 탐색 켜기" not in str(objective.get("nextAction") or "")
        or field.get("enabled") is not True
        or field.get("stepCount") != 0
        or field.get("lastMap") != "map2_14j"
        or state.get("fieldEncounterMenuLabel") != "전투 탐색 끄기 0/6"
        or mode.get("enabled") is not True
        or mode.get("source") != "objective-action"
        or mode.get("originalEncounterRuntimeImplemented") is not False
        or mode_auto_save.get("saved") is not True
        or mode_auto_save.get("source") != "field-encounter-enable"
        or mode_auto_save.get("payloadMap") != "map2_14j"
        or mode_auto_save.get("fieldEncounter", {}).get("enabled") is not True
        or mode_auto_save.get("fieldEncounter", {}).get("lastMap") != "map2_14j"
        or state.get("savedPayloadMap") != "map2_14j"
        or saved_field.get("enabled") is not True
        or saved_field.get("stepCount") != 0
        or saved_field.get("lastMap") != "map2_14j"
        or state.get("trialTransitions") != "routeAssist"
        or state.get("selectedRouteGoal") != "map2_14j"
        or state.get("routePathValue") != "map2_14j"
        or "encounter=1" not in str(state.get("search") or "")
        or "전투 0/6" not in play_hud
    ):
        raise WebDriverError(f"candidate field-encounter objective action is incomplete: {state!r}")


def verify_quick_objective_sound_state(
    state: dict,
    expected_source: str,
    expected_action: str,
    expected_route_target: str,
) -> None:
    quick_action = state.get("quickAction") or {}
    quick_sound = quick_action.get("sound") or state.get("quickSound") or state.get("lastQuickObjectiveSound") or {}
    sound_log = state.get("quickObjectiveSoundLog") or []
    last_sound = state.get("lastSound") or {}
    menu_confirm_count = state.get("menuConfirmSoundCount")
    log_length = state.get("quickObjectiveSoundLogLength")
    if (
        quick_sound.get("source") != "quick-objective-sound"
        or quick_sound.get("inputSource") != expected_source
        or quick_sound.get("action") != expected_action
        or quick_sound.get("routeTarget") != expected_route_target
        or quick_sound.get("soundKey") != "menuConfirm"
        or quick_sound.get("soundSrc") != "../extract_wlk/04.wav"
        or quick_sound.get("soundPlayed") is not True
        or quick_sound.get("browserQuickObjectiveSoundImplemented") is not True
        or quick_sound.get("originalDirectSoundTimingImplemented") is not False
        or quick_sound.get("originalStoryFlagRuntimeImplemented") is not False
        or menu_confirm_count not in (0, 1)
        or log_length not in (0, 1)
        or (sound_log and (sound_log[-1] or {}).get("source") != "quick-objective-sound")
        or (sound_log and (sound_log[-1] or {}).get("inputSource") != expected_source)
        or (sound_log and (sound_log[-1] or {}).get("action") != expected_action)
        or (menu_confirm_count == 1 and last_sound.get("key") != "menuConfirm")
        or (menu_confirm_count == 1 and last_sound.get("src") != "../extract_wlk/04.wav")
    ):
        raise WebDriverError(f"quick objective sound state is incomplete: {state!r}")


def verify_quick_objective_keyboard_state(state: dict) -> None:
    quick_action = state.get("quickAction") or {}
    objective = quick_action.get("objective") or {}
    before_hud = " ".join(str(line) for line in state.get("beforeHudLines") or [])
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map1_01a"
        or quick_action.get("source") != "keyboard"
        or quick_action.get("action") != "route-next"
        or quick_action.get("handled") is not True
        or quick_action.get("routeTarget") != "map2_02d"
        or quick_action.get("originalRoutePromotionImplemented") is not False
        or quick_action.get("originalStoryFlagRuntimeImplemented") is not False
        or objective.get("title") != "후보 목표 map2_02d"
        or state.get("beforeMap") != "map1_02b"
        or state.get("beforeButtonHidden") is not False
        or "목표 실행: 후보 목표 map2_02d" not in str(state.get("beforeButtonTitle") or "")
        or "후보 map2_02d" not in before_hud
        or "다음 map1_01a" not in before_hud
        or state.get("routeGoalParam") != "map2_02d"
        or state.get("routeAutoSaveParam") != "1"
        or state.get("savedPayloadMap") != "map1_01a"
        or "trialTransitions=routeAssist" not in str(state.get("search") or "")
        or "후보 map2_02d" not in play_hud
    ):
        raise WebDriverError(f"quick objective keyboard state is incomplete: {state!r}")
    verify_quick_objective_sound_state(state, "keyboard", "route-next", "map2_02d")


def verify_quick_objective_toolbar_next_state(state: dict) -> None:
    quick_action = state.get("quickAction") or {}
    objective = quick_action.get("objective") or {}
    before_hud = " ".join(str(line) for line in state.get("beforeHudLines") or [])
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map1_01a"
        or state.get("clicked") is not True
        or quick_action.get("source") != "objective-action-button"
        or quick_action.get("action") != "route-next"
        or quick_action.get("handled") is not True
        or quick_action.get("routeTarget") != "map2_02d"
        or quick_action.get("originalRoutePromotionImplemented") is not False
        or quick_action.get("originalStoryFlagRuntimeImplemented") is not False
        or objective.get("title") != "후보 목표 map2_02d"
        or state.get("beforeMap") != "map1_02b"
        or state.get("beforeObjectiveButtonHidden") is not False
        or state.get("beforeObjectiveButtonDisabled") is not False
        or state.get("beforeObjectiveButtonText") != "목표"
        or "목표 실행: 후보 목표 map2_02d" not in str(state.get("beforeObjectiveButtonTitle") or "")
        or "map1_01a" not in str(state.get("beforeObjectiveButtonTitle") or "")
        or "후보 map2_02d" not in before_hud
        or "다음 map1_01a" not in before_hud
        or state.get("routeGoalParam") != "map2_02d"
        or state.get("routeAutoSaveParam") != "1"
        or state.get("savedPayloadMap") != "map1_01a"
        or "trialTransitions=routeAssist" not in str(state.get("search") or "")
        or "후보 map2_02d" not in play_hud
        or state.get("objectiveButtonHidden") is not False
        or "목표 실행: 후보 목표 map2_02d" not in str(state.get("objectiveButtonTitle") or "")
    ):
        raise WebDriverError(f"quick objective toolbar route-next state is incomplete: {state!r}")
    verify_quick_objective_sound_state(state, "objective-action-button", "route-next", "map2_02d")


def verify_quick_objective_button_next_state(state: dict) -> None:
    quick_action = state.get("quickAction") or {}
    objective = quick_action.get("objective") or {}
    before_hud = " ".join(str(line) for line in state.get("beforeHudLines") or [])
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map1_01a"
        or state.get("clicked") is not True
        or quick_action.get("source") != "virtual-objective-button"
        or quick_action.get("action") != "route-next"
        or quick_action.get("handled") is not True
        or quick_action.get("routeTarget") != "map2_02d"
        or quick_action.get("originalRoutePromotionImplemented") is not False
        or quick_action.get("originalStoryFlagRuntimeImplemented") is not False
        or objective.get("title") != "후보 목표 map2_02d"
        or state.get("beforeMap") != "map1_02b"
        or state.get("beforeButtonHidden") is not False
        or "목표 실행: 후보 목표 map2_02d" not in str(state.get("beforeButtonTitle") or "")
        or "map1_01a" not in str(state.get("beforeButtonTitle") or "")
        or "후보 map2_02d" not in before_hud
        or "다음 map1_01a" not in before_hud
        or state.get("routeGoalParam") != "map2_02d"
        or state.get("routeAutoSaveParam") != "1"
        or state.get("savedPayloadMap") != "map1_01a"
        or "trialTransitions=routeAssist" not in str(state.get("search") or "")
        or "후보 map2_02d" not in play_hud
        or state.get("buttonHidden") is not False
        or "목표 실행: 후보 목표 map2_02d" not in str(state.get("buttonTitle") or "")
    ):
        raise WebDriverError(f"quick objective virtual button route-next state is incomplete: {state!r}")
    verify_quick_objective_sound_state(state, "virtual-objective-button", "route-next", "map2_02d")


def verify_quick_objective_button_state(state: dict) -> None:
    quick_action = state.get("quickAction") or {}
    objective = quick_action.get("objective") or {}
    field = state.get("fieldEncounter") or {}
    mode = state.get("fieldEncounterMode") or {}
    mode_auto_save = state.get("fieldEncounterModeAutoSave") or {}
    saved_field = state.get("savedFieldEncounter") or {}
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map2_14j"
        or state.get("clicked") is not True
        or quick_action.get("source") != "virtual-objective-button"
        or quick_action.get("action") != "field-encounter-enable"
        or quick_action.get("handled") is not True
        or quick_action.get("routeTarget") != "map2_14j"
        or quick_action.get("originalEncounterRuntimeImplemented") is not False
        or quick_action.get("originalStoryFlagRuntimeImplemented") is not False
        or objective.get("title") != "후보 목표 map2_14j"
        or state.get("beforeButtonHidden") is not False
        or "목표 실행: 후보 목표 map2_14j" not in str(state.get("beforeButtonTitle") or "")
        or state.get("buttonHidden") is not False
        or "걸어서 필드 전투 0/6" not in str(state.get("buttonTitle") or "")
        or field.get("enabled") is not True
        or field.get("stepCount") != 0
        or field.get("lastMap") != "map2_14j"
        or mode.get("source") != "objective-action"
        or mode_auto_save.get("saved") is not True
        or mode_auto_save.get("source") != "field-encounter-enable"
        or mode_auto_save.get("payloadMap") != "map2_14j"
        or state.get("savedPayloadMap") != "map2_14j"
        or saved_field.get("enabled") is not True
        or saved_field.get("lastMap") != "map2_14j"
        or "encounter=1" not in str(state.get("search") or "")
        or "전투 0/6" not in play_hud
    ):
        raise WebDriverError(f"quick objective virtual button state is incomplete: {state!r}")
    verify_quick_objective_sound_state(state, "virtual-objective-button", "field-encounter-enable", "map2_14j")


def verify_quick_objective_toolbar_state(state: dict) -> None:
    quick_action = state.get("quickAction") or {}
    objective = quick_action.get("objective") or {}
    before_hud = " ".join(str(line) for line in state.get("beforeHudLines") or [])
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map2_02d"
        or state.get("clicked") is not True
        or quick_action.get("source") != "objective-action-button"
        or quick_action.get("action") != "route-continuation"
        or quick_action.get("handled") is not True
        or quick_action.get("routeTarget") != "map2_02d"
        or quick_action.get("originalRoutePromotionImplemented") is not False
        or quick_action.get("originalStoryFlagRuntimeImplemented") is not False
        or objective.get("title") != "후보 목표 map2_02d"
        or state.get("beforeMap") != "map2_02d"
        or state.get("beforeObjectiveButtonHidden") is not False
        or state.get("beforeObjectiveButtonDisabled") is not False
        or state.get("beforeObjectiveButtonText") != "목표"
        or "목표 실행: 후보 목표 map2_02d" not in str(state.get("beforeObjectiveButtonTitle") or "")
        or "map2_09g" not in str(state.get("beforeObjectiveButtonTitle") or "")
        or "다음 목표 map2_09g" not in before_hud
        or state.get("trialTransitions") != "routeAssist"
        or state.get("selectedRouteGoal") != "map2_09g"
        or state.get("routePathValue") != "map2_09g"
        or state.get("transitionTargetParam") != ""
        or state.get("routeGoalParam") != "map2_02d"
        or state.get("routeAutoSaveParam") != ""
        or state.get("routeNextText") != "후보"
        or "map2_02d -> map2_09g" not in str(state.get("routeNextTitle") or "")
        or state.get("objectiveButtonHidden") is not False
        or state.get("objectiveButtonDisabled") is not False
        or "목표 실행: 후보 목표 map2_09g" not in str(state.get("objectiveButtonTitle") or "")
        or "후보 map2_09g" not in play_hud
        or "다음 map2_09g" not in play_hud
    ):
        raise WebDriverError(f"quick objective toolbar state is incomplete: {state!r}")
    verify_quick_objective_sound_state(state, "objective-action-button", "route-continuation", "map2_02d")


def verify_route_continuation_entry_state(
    state: dict,
    expected_source: str,
    expected_target: str,
    expected_count: int,
    expected_next_target: str | None = None,
) -> None:
    auto_save = state.get("routeAssistAutoSave") 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_progress = auto_save.get("progress") or {}
    auto_counts = auto_progress.get("counts") or {}
    auto_field = auto_save.get("fieldEncounter") or {}
    progress_event = auto_save.get("progressEvent") or {}
    detail = progress_event.get("detail") or {}
    in_place = state.get("routeAssistInPlaceTransition") or {}
    field = state.get("fieldEncounter") or {}
    saved_field = state.get("savedFieldEncounter") or {}
    next_continuation_item = state.get("nextContinuationItem") or {}
    path_labels = state.get("routePathLabels") or []
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    before_hud = " ".join(str(line) for line in state.get("beforeHudLines") or [])
    expected_edge = f"{expected_source} -> {expected_target}"
    expected_id = f"{expected_source}->{expected_target}"
    expected_current_progress_text = f"후보 진행 {expected_count}/{expected_count + 1}"
    expected_next_progress_text = (
        f"후보 진행 {expected_count}/{expected_count + 2}"
        if expected_next_target
        else expected_current_progress_text
    )
    expected_route_next_text = {"이어가기"} if expected_next_target else {"도착", "완료"}
    if not expected_next_target and saved_counts.get("route-clear"):
        expected_route_next_text.add("클리어")
    if (
        state.get("scene") != "map"
        or state.get("map") != expected_target
        or state.get("entered") is not True
        or state.get("beforeMap") != expected_source
        or state.get("beforeSelectedRouteGoal") != expected_target
        or state.get("beforeSelectedTransitionTarget") != expected_target
        or state.get("beforeRoutePathValue") != expected_target
        or state.get("beforeRouteNextText") != "진입"
        or expected_edge not in str(state.get("beforeRouteNextTitle") or "")
        or f"후보 {expected_target}" not in before_hud
        or f"다음 {expected_target}" not in before_hud
        or state.get("trialTransitions") != "routeAssist"
        or state.get("selectedRouteGoal") != expected_target
        or state.get("routePathValue") != expected_target
        or state.get("routeGoalParam") != expected_target
        or state.get("routeAutoSaveParam") != "1"
        or state.get("routeStepSourceParam") != expected_source
        or state.get("routeStepTargetParam") != expected_target
        or state.get("routeStepKindParam") != "save-selector-trial"
        or f"map={expected_target}" not in str(state.get("search") or "")
        or "trialTransitions=routeAssist" not in str(state.get("search") or "")
        or f"routeGoal={expected_target}" not in str(state.get("search") or "")
        or f"routeStepSource={expected_source}" not in str(state.get("search") or "")
        or f"routeStepTarget={expected_target}" not in str(state.get("search") or "")
        or state.get("routeNextText") not in expected_route_next_text
        or expected_target not in str(state.get("routeNextTitle") or "")
        or expected_next_progress_text not in str(state.get("routeNextTitle") or "")
        or expected_current_progress_text not in play_hud
        or not any(expected_current_progress_text in str(label) for label in path_labels)
        or f"후보 {expected_target}" not in play_hud
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "routeAutoSave"
        or auto_save.get("map") != expected_target
        or auto_save.get("payloadMap") != expected_target
        or auto_save.get("sourceMap") != expected_source
        or auto_save.get("targetMap") != expected_target
        or auto_save.get("candidateKind") != "save-selector-trial"
        or auto_save.get("directTargetUrl") is not True
        or counts.get("route-candidate") != expected_count
        or saved_counts.get("route-candidate") != expected_count
        or auto_counts.get("route-candidate") != expected_count
        or progress_event.get("kind") != "route-candidate"
        or progress_event.get("id") != expected_id
        or progress_event.get("map") != expected_target
        or detail.get("sourceMap") != expected_source
        or detail.get("targetMap") != expected_target
        or detail.get("trigger") != "route-assist-direct-target"
        or detail.get("trialTransitionMode") != "routeAssist"
        or detail.get("candidateKind") != "save-selector-trial"
        or detail.get("directTargetUrl") is not True
        or detail.get("routeAutoSave") is not True
        or detail.get("originalRoutePromotionImplemented") is not False
        or auto_save.get("originalRoutePromotionImplemented") is not False
        or in_place.get("source") != "route-assist-in-place-transition"
        or in_place.get("handled") is not True
        or in_place.get("autoSaved") is not True
        or in_place.get("routeAutoSave") is not True
        or in_place.get("directTargetUrl") is not True
        or in_place.get("sourceMap") != expected_source
        or in_place.get("targetMap") != expected_target
        or in_place.get("stepKind") != "save-selector-trial"
        or in_place.get("trialTransitionMode") != "routeAssist"
        or in_place.get("candidateKind") != "save-selector-trial"
        or in_place.get("browserRouteAssistInPlaceTransitionImplemented") is not True
        or in_place.get("originalRoutePromotionImplemented") is not False
        or in_place.get("originalStoryFlagRuntimeImplemented") is not False
        or state.get("savedPayloadMap") != expected_target
        or state.get("savedRouteGoal") != expected_target
        or state.get("savedTrialTransitions") != "routeAssist"
        or state.get("savedRouteAutoSaveSource") != "routeAutoSave"
    ):
        raise WebDriverError(f"candidate route continuation entry is incomplete: {state!r}")
    expected_feedback_text = f"{expected_current_progress_text} {expected_target}"
    verify_route_progress_feedback(
        state,
        expected_target,
        expected_source,
        expected_target,
        expected_feedback_text,
        expected_count,
        expected_count + 1,
        expected_trigger="route-assist-direct-target",
        expected_candidate_kind="save-selector-trial",
        expected_route_auto_save=True,
        expected_direct_target_url=True,
    )
    auto_feedback = auto_save.get("routeProgressFeedback") or {}
    if (
        auto_feedback.get("source") != "route-progress-feedback"
        or auto_feedback.get("text") != expected_feedback_text
        or auto_feedback.get("sourceMap") != expected_source
        or auto_feedback.get("targetMap") != expected_target
        or auto_feedback.get("routeCandidateCount") != expected_count
        or auto_feedback.get("routeProgressCount") != expected_count + 1
        or auto_feedback.get("routeAutoSave") is not True
        or auto_feedback.get("directTargetUrl") is not True
        or auto_feedback.get("routeProgressSound") != "menuConfirm"
        or not str(auto_feedback.get("routeProgressSoundSrc") or "").endswith("/extract_wlk/04.wav")
        or auto_feedback.get("routeProgressSoundPlayed") is not True
        or auto_feedback.get("browserRouteProgressFeedbackImplemented") is not True
        or auto_feedback.get("originalRoutePromotionImplemented") is not False
    ):
        raise WebDriverError(f"candidate route continuation feedback is incomplete: {state!r}")
    if expected_next_target:
        if (
            f"다음 목표 {expected_next_target}" not in play_hud
            or next_continuation_item.get("command") != "continueRouteAssistPath"
            or next_continuation_item.get("name") != f"후보 이어가기 {expected_next_target}"
            or next_continuation_item.get("routeTarget") != expected_next_target
            or next_continuation_item.get("routeNextTarget") != expected_next_target
        ):
            raise WebDriverError(f"candidate route continuation next item is incomplete: {state!r}")
    if "encounter=1" in str(state.get("search") or ""):
        if (
            field.get("enabled") is not True
            or field.get("stepCount") != 0
            or field.get("lastMap") != expected_target
            or saved_field.get("enabled") is not True
            or saved_field.get("stepCount") != 0
            or saved_field.get("lastMap") != expected_target
            or auto_field.get("enabled") is not True
            or auto_field.get("stepCount") != 0
            or auto_field.get("lastMap") != expected_target
            or state.get("fieldEncounterMenuLabel") != "전투 탐색 끄기 0/6"
            or "전투 0/6" not in play_hud
        ):
            raise WebDriverError(f"candidate route continuation field encounter state is incomplete: {state!r}")


def verify_route_continuation_title_restore_state(
    state: dict,
    title_label: str,
    expected_map: str,
    expected_tile: dict[str, int],
    expected_next_target: str | None,
    expected_count: int,
) -> None:
    continuation_item = state.get("continuationItem") or {}
    tile = state.get("tile") or {}
    saved_tile = state.get("savedPayloadTile") or {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    saved_progress = state.get("savedProgress") or {}
    saved_counts = saved_progress.get("counts") or {}
    field = state.get("fieldEncounter") or {}
    saved_field = state.get("savedFieldEncounter") or {}
    path_labels = state.get("routePathLabels") or []
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    labels = state.get("labels") or []
    has_continuation = bool(continuation_item.get("command"))
    expected_label = f"후보 이어가기 {expected_next_target}" if expected_next_target else ""
    expected_current_progress_text = f"후보 진행 {expected_count}/{expected_count + 1}"
    expected_next_progress_text = (
        f"후보 진행 {expected_count}/{expected_count + 2}"
        if expected_next_target
        else expected_current_progress_text
    )
    expected_route_next_text = {"이어가기"} if expected_next_target else {"도착", "완료"}
    if not expected_next_target and saved_counts.get("route-clear"):
        expected_route_next_text.add("클리어")
    if (
        state.get("titleContinue") is not True
        or f"이어하기 {expected_map}" not in title_label
        or state.get("scene") != "map"
        or state.get("map") != expected_map
        or tile.get("x") != expected_tile.get("x")
        or tile.get("y") != expected_tile.get("y")
        or state.get("quickLoadHidden") is not False
        or state.get("quickLoadText") != "임시 불러오기"
        or state.get("trialTransitions") != "routeAssist"
        or state.get("selectedRouteGoal") != expected_map
        or state.get("routePathValue") != expected_map
        or state.get("routeNextText") not in expected_route_next_text
        or expected_map not in str(state.get("routeNextTitle") or "")
        or expected_next_progress_text not in str(state.get("routeNextTitle") or "")
        or expected_current_progress_text not in play_hud
        or not any(expected_current_progress_text in str(label) for label in path_labels)
        or f"후보 {expected_map}" not in play_hud
        or (expected_next_target and f"다음 목표 {expected_next_target}" not in play_hud)
        or (expected_next_target and continuation_item.get("command") != "continueRouteAssistPath")
        or (expected_next_target and continuation_item.get("name") != expected_label)
        or (expected_next_target and continuation_item.get("routeTarget") != expected_next_target)
        or (expected_next_target and continuation_item.get("routeNextTarget") != expected_next_target)
        or (expected_next_target and expected_label not in labels)
        or (expected_next_target is None and has_continuation)
        or (expected_next_target is None and "후보 이어가기" in " ".join(str(label) for label in labels))
        or (expected_next_target is None and "다음 목표" in play_hud)
        or state.get("savedPayloadMap") != expected_map
        or saved_tile.get("x") != expected_tile.get("x")
        or saved_tile.get("y") != expected_tile.get("y")
        or state.get("savedRouteGoal") != expected_map
        or state.get("savedTrialTransitions") != "routeAssist"
        or counts.get("route-candidate") != expected_count
        or saved_counts.get("route-candidate") != expected_count
    ):
        raise WebDriverError(f"candidate route continuation title restore is incomplete: {state!r}")
    if "encounter=1" in str(state.get("search") or ""):
        if (
            field.get("enabled") is not True
            or field.get("stepCount") != 0
            or field.get("lastMap") != expected_map
            or saved_field.get("enabled") is not True
            or saved_field.get("stepCount") != 0
            or saved_field.get("lastMap") != expected_map
            or state.get("fieldEncounterMenuLabel") != "전투 탐색 끄기 0/6"
            or "전투 0/6" not in play_hud
        ):
            raise WebDriverError(f"candidate route continuation title restore field encounter state is incomplete: {state!r}")


def verify_route_continuation_field_encounter_title_restore_state(
    state: dict,
    title_label: str,
    expected_map: str,
    expected_tile: dict[str, int],
    expected_count: int,
) -> None:
    verify_route_continuation_title_restore_state(
        state,
        title_label,
        expected_map,
        expected_tile,
        None,
        expected_count,
    )
    counts = ((state.get("progress") or {}).get("counts") or {})
    saved_counts = ((state.get("savedProgress") or {}).get("counts") or {})
    saved_story_flags = state.get("savedStoryFlags") or {}
    completion = state.get("completion") or {}
    route_completion = completion.get("route") or {}
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    cleared = route_completion.get("routeClearRecorded") is True
    expected_completion_text = "클리어" if cleared else "완료"
    if (
        "전투 후보" not in title_label
        or f"{expected_completion_text} {expected_map}" not in title_label
        or (cleared and f"완료 {expected_map}" in title_label)
        or counts.get("route-candidate") != expected_count
        or counts.get("battle-start") != 1
        or counts.get("field-encounter") != 1
        or counts.get("field-encounter-victory") != 1
        or counts.get("battle-victory", 0) != 0
        or saved_counts.get("route-candidate") != expected_count
        or saved_counts.get("battle-start") != 1
        or saved_counts.get("field-encounter") != 1
        or saved_counts.get("field-encounter-victory") != 1
        or saved_counts.get("battle-victory", 0) != 0
        or completion.get("completed") is not True
        or route_completion.get("source") != "prototype-route-assist-completion"
        or route_completion.get("completed") is not True
        or route_completion.get("currentMap") != expected_map
        or route_completion.get("selectedRouteGoal") != expected_map
        or route_completion.get("trialTransitions") != "routeAssist"
        or route_completion.get("routeCandidateCount") != expected_count
        or route_completion.get("fieldEncounterCount") != 1
        or route_completion.get("fieldEncounterVictoryCount") != 1
        or route_completion.get("originalRoutePromotionImplemented") is not False
        or route_completion.get("originalEncounterRuntimeImplemented") is not False
        or f"{expected_completion_text} {expected_map}" not in play_hud
        or "전투 0/6" not in play_hud
        or "저장 있음" not in play_hud
    ):
        raise WebDriverError(f"candidate route continuation field encounter title restore is incomplete: {state!r}")


def verify_route_completion_gate_title_restore_state(
    state: dict,
    title_label: str,
    expected_map: str,
    expected_tile: dict[str, int],
    expected_count: int,
) -> None:
    verify_route_continuation_field_encounter_title_restore_state(
        state,
        title_label,
        expected_map,
        expected_tile,
        expected_count,
    )
    counts = ((state.get("progress") or {}).get("counts") or {})
    saved_counts = ((state.get("savedProgress") or {}).get("counts") or {})
    saved_story_flags = state.get("savedStoryFlags") or {}
    completion = state.get("completion") or {}
    route_completion = completion.get("route") or {}
    playable_gate = completion.get("playableGate") or {}
    completion_item = state.get("completionItem") or {}
    labels = state.get("labels") or []
    play_hud = " ".join(str(line) for line in state.get("playHudLines") or [])
    cleared = route_completion.get("routeClearRecorded") is True
    complete_story_key = f"story:route-complete:{expected_map}:route-complete:{expected_map}"
    expected_next_text = "클리어" if cleared else "완료"
    expected_objective = "후보 루트 클리어" if cleared else "후보 루트 완료"
    expected_next_action = "클리어 요약 확인" if cleared else "완료 버튼으로 결과 확인"
    if (
        counts.get("route-complete") != 1
        or saved_counts.get("route-complete") != 1
        or complete_story_key not in story_flag_keys(saved_story_flags)
        or route_completion.get("routeCompleteCount") != 1
        or route_completion.get("routeCompleteRecorded") is not True
        or completion.get("playableGateOpen") is not True
        or playable_gate.get("source") != "prototype-playable-route-gate"
        or playable_gate.get("status") != "open"
        or playable_gate.get("opened") is not True
        or playable_gate.get("canFinishPrototype") is not True
        or playable_gate.get("label") != f"웹 후보 루트 완료 {expected_map}"
        or playable_gate.get("selectedRouteGoal") != expected_map
        or playable_gate.get("routeCompleteCount") != 1
        or playable_gate.get("originalFullGameCompletionImplemented") is not False
        or state.get("routeNextText") != expected_next_text
        or f"{expected_objective} {expected_map}" not in play_hud
        or f"다음 {expected_next_action}" not in play_hud
    ):
        raise WebDriverError(f"candidate route completion gate title restore is incomplete: {state!r}")
    verify_story_flags(
        saved_story_flags,
        {complete_story_key},
        {"route-complete": 1},
        "route completion gate title restore",
    )
    if not cleared and (
        completion_item.get("command") != "showRouteCompletionNotice"
        or completion_item.get("name") != f"후보 완료 {expected_map}"
        or f"후보 완료 {expected_map}" not in labels
    ):
        raise WebDriverError(f"candidate route completion menu item is incomplete: {state!r}")
    if cleared and (
        completion_item
        or f"후보 완료 {expected_map}" in labels
    ):
        raise WebDriverError(f"candidate route clear state kept stale completion menu item: {state!r}")


def verify_route_clear_gate_title_restore_state(
    state: dict,
    title_label: str,
    expected_map: str,
    expected_tile: dict[str, int],
    expected_count: int,
) -> None:
    verify_route_completion_gate_title_restore_state(
        state,
        title_label,
        expected_map,
        expected_tile,
        expected_count,
    )
    counts = ((state.get("progress") or {}).get("counts") or {})
    saved_counts = ((state.get("savedProgress") or {}).get("counts") or {})
    saved_story_flags = state.get("savedStoryFlags") or {}
    route_completion = ((state.get("completion") or {}).get("route") or {})
    playable_gate = ((state.get("completion") or {}).get("playableGate") or {})
    clear_item = state.get("clearItem") or {}
    labels = state.get("labels") or []
    expected_story_keys = {
        f"story:route-complete:{expected_map}:route-complete:{expected_map}",
        f"story:route-clear:{expected_map}:route-clear:{expected_map}",
    }
    if (
        f"클리어 {expected_map}" not in title_label
        or counts.get("route-clear") != 1
        or saved_counts.get("route-clear") != 1
        or not expected_story_keys.issubset(story_flag_keys(saved_story_flags))
        or route_completion.get("routeClearCount") != 1
        or route_completion.get("routeClearRecorded") is not True
        or playable_gate.get("routeClearCount") != 1
        or clear_item.get("command") != "showRouteClearSummary"
        or clear_item.get("name") != f"후보 클리어 {expected_map}"
        or clear_item.get("usable") is not True
        or labels.count(f"후보 클리어 {expected_map}") != 1
    ):
        raise WebDriverError(f"candidate route clear gate title restore is incomplete: {state!r}")
    verify_story_flags(
        saved_story_flags,
        expected_story_keys,
        {"route-complete": 1, "route-clear": 1},
        "route clear gate title restore",
    )


def verify_default_prototype_map_exit_state(state: dict) -> None:
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    saved_progress = state.get("savedProgress") or {}
    saved_counts = saved_progress.get("counts") or {}
    record = state.get("progressRecord") or {}
    detail = record.get("detail") or {}
    before = state.get("before") or {}
    prompt = before.get("prompt") or {}
    feedback = state.get("routeProgressFeedbackLast") or {}
    feedback_render = state.get("routeProgressFeedbackLastRender") or {}
    marker = state.get("prototypeMapExitTransition") or {}
    expected_block_reasons = expected_route_trial_block_reasons("map1_01a", "map2_02d")
    auto_save = state.get("autoSave") or {}
    auto_progress = auto_save.get("progress") or {}
    auto_route_state = auto_save.get("routeState") or {}
    if (
        state.get("handled") is not True
        or state.get("saved") is not True
        or state.get("scene") != "map"
        or state.get("map") != "map2_02d"
        or state.get("trialTransitions") != ""
        or "trialTransitions=" in str(state.get("search") or "")
        or before.get("map") != "map1_01a"
        or before.get("trialTransitions") != ""
        or before.get("prototypeMapExitTransitions") is not True
        or before.get("mapExitGameplayTransitions") is not True
        or "map2_02d" not in (before.get("targets") or [])
        or prompt.get("kind") != "route-candidate"
        or prompt.get("targetMap") != "map2_02d"
        or prompt.get("originalRoutePromotionImplemented") is not False
        or progress.get("total") != 1
        or counts.get("route-candidate") != 1
        or saved_counts.get("route-candidate") != 1
        or record.get("kind") != "route-candidate"
        or record.get("id") != "map1_01a->map2_02d"
        or record.get("map") != "map1_01a"
        or detail.get("sourceMap") != "map1_01a"
        or detail.get("targetMap") != "map2_02d"
        or detail.get("trigger") != "movement"
        or detail.get("trialTransitionMode") != "prototypeMapExit"
        or detail.get("candidateKind") != "map-exit"
        or detail.get("side") != "top"
        or detail.get("autoTrigger") is not True
        or detail.get("prototypeMapExitTransitionImplemented") is not True
        or detail.get("originalRoutePromotionImplemented") is not False
        or marker.get("source") != "prototype-map-exit-transition"
        or marker.get("trialTransitionMode") != "prototypeMapExit"
        or marker.get("prototypeMapExitTransitionImplemented") is not True
        or marker.get("originalRoutePromotionImplemented") is not False
        or state.get("payloadMap") != "map2_02d"
        or (state.get("payloadTile") or {}).get("x") != 47
        or (state.get("payloadTile") or {}).get("y") != 47
        or state.get("savedTrialTransitions") != ""
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "route-candidate"
        or auto_save.get("payloadMap") != "map2_02d"
        or auto_progress.get("counts", {}).get("route-candidate") != 1
        or auto_route_state.get("trialTransitions") != ""
        or feedback.get("source") != "route-progress-feedback"
        or feedback.get("trialTransitionMode") != "prototypeMapExit"
        or feedback.get("prototypeMapExitTransitionImplemented") is not True
        or feedback.get("originalRoutePromotionImplemented") is not False
        or feedback_render.get("source") != "route-progress-feedback"
        or feedback_render.get("trialTransitionMode") != "prototypeMapExit"
        or feedback_render.get("prototypeMapExitTransitionImplemented") is not True
    ):
        raise WebDriverError(f"default prototype map exit state is incomplete: {state!r}")
    if expected_block_reasons is not None:
        verify_trial_blocker_fields(detail, expected_block_reasons, "default prototype map exit detail")
        verify_trial_blocker_fields(feedback, expected_block_reasons, "default prototype map exit feedback")
        verify_trial_blocker_fields(prompt, expected_block_reasons, "default prototype map exit prompt")


def verify_route_progress_state(state: dict, restored: bool = False) -> None:
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    labels = state.get("labels") or []
    path_labels = state.get("routePathLabels") or []
    lines = "\n".join(state.get("activeLines") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map2_02d"
        or state.get("activeId") != "prototype-progress:map2_02d"
        or progress.get("total") != 1
        or counts.get("route-candidate") != 1
        or progress.get("originalStoryFlagRuntimeImplemented") is not False
        or "진행 목표 1" not in labels
        or not any("후보 진행 1/2" in str(label) for label in path_labels)
        or "후보 진행 1/2" not in str(state.get("routeNextTitle") or "")
        or "목표 후보 목표" not in lines
        or "다음 " not in lines
        or "map1_01a->map2_02d" not in lines
        or "후보 진행" not in lines
        or "map=map2_02d" not in str(state.get("search") or "")
        or (not restored and "trialTransitions=routeAssist" not in str(state.get("search") or ""))
        or (restored and state.get("trialTransitions") != "routeAssist")
    ):
        raise WebDriverError(f"candidate route progress state is incomplete: {state!r}")
    if restored:
        if state.get("loaded") is not True:
            raise WebDriverError(f"candidate route progress restore did not load: {state!r}")
        return

    record = state.get("progressRecord") or {}
    detail = record.get("detail") or {}
    saved_progress = state.get("savedProgress") or {}
    saved_counts = saved_progress.get("counts") or {}
    before = state.get("before") or {}
    expected_block_reasons = expected_route_trial_block_reasons("map1_01a", "map2_02d")
    verify_route_candidate_auto_save(
        state.get("autoSave") or {},
        "map1_01a",
        "map2_02d",
        1,
        {"x": 18, "y": 0},
        {"x": 47, "y": 47},
        expected_block_reasons,
    )
    verify_route_progress_feedback(
        state,
        "map2_02d",
        "map1_01a",
        "map2_02d",
        f"후보 진행 1/2 map2_02d · {TRIAL_BLOCKER_SHORT}",
        1,
        2,
        expected_trigger="movement",
        expected_candidate_kind="map-exit",
        expected_route_auto_save=False,
        expected_direct_target_url=False,
        expected_block_reasons=expected_block_reasons,
    )
    if (
        state.get("handled") is not True
        or state.get("saved") is not True
        or before.get("map") != "map1_01a"
        or "map2_02d" not in (before.get("targets") or [])
        or record.get("kind") != "route-candidate"
        or record.get("id") != "map1_01a->map2_02d"
        or record.get("map") != "map1_01a"
        or record.get("originalStoryFlagRuntimeImplemented") is not False
        or detail.get("sourceMap") != "map1_01a"
        or detail.get("targetMap") != "map2_02d"
        or detail.get("trigger") != "movement"
        or detail.get("candidateKind") != "map-exit"
        or detail.get("side") != "top"
        or detail.get("autoTrigger") is not True
        or detail.get("originalRoutePromotionImplemented") is not False
        or "trial-only:5" not in lines
        or saved_counts.get("route-candidate") != 1
        or state.get("payloadMap") != "map2_02d"
        or (state.get("payloadTile") or {}).get("x") != 47
        or (state.get("payloadTile") or {}).get("y") != 47
    ):
        raise WebDriverError(f"candidate route progress record is incomplete: {state!r}")
    if expected_block_reasons is not None:
        verify_trial_blocker_fields(detail, expected_block_reasons, "route progress detail")


def verify_route_source_prompt_state(state: dict) -> None:
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    prompt = state.get("prompt") or {}
    prompt_text = str(prompt.get("text") or "")
    expected_block_reasons = expected_route_trial_block_reasons("map1_01a", "map2_02d")
    path_labels = state.get("routePathLabels") or []
    saved_progress = state.get("savedProgress") or {}
    saved_counts = saved_progress.get("counts") or {}
    if (
        state.get("scene") != "map"
        or state.get("map") != "map1_01a"
        or (state.get("tile") or {}).get("x") != 18
        or (state.get("tile") or {}).get("y") != 0
        or counts.get("route-candidate") != 1
        or saved_counts.get("route-candidate") != 1
        or prompt.get("kind") != "route-candidate"
        or prompt.get("progressKind") != "route-candidate"
        or prompt.get("targetMap") != "map2_02d"
        or prompt.get("id") != "map1_01a->map2_02d"
        or prompt.get("completed") is not True
        or "Move/Enter -> 후보 완료 map2_02d" not in prompt_text
        or TRIAL_BLOCKER_SHORT not in prompt_text
        or "후보 완료 1/1" not in str(state.get("routeNextTitle") or "")
        or not any("후보 완료 1/1" in str(label) for label in path_labels)
    ):
        raise WebDriverError(f"candidate route source prompt is incomplete: {state!r}")
    if expected_block_reasons is not None:
        verify_trial_blocker_fields(prompt, expected_block_reasons, "route source prompt")


def verify_route_chain_state(state: dict, restored: bool = False) -> None:
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    labels = state.get("labels") or []
    path_labels = state.get("routePathLabels") or []
    lines = "\n".join(state.get("activeLines") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map2_18d"
        or state.get("activeId") != "prototype-progress:map2_18d"
        or progress.get("total") != 2
        or counts.get("route-candidate") != 2
        or progress.get("originalStoryFlagRuntimeImplemented") is not False
        or "진행 목표 2" not in labels
        or not any("후보 완료 2/2" in str(label) for label in path_labels)
        or "후보 완료 2/2" not in str(state.get("routeNextTitle") or "")
        or state.get("routeNextText") != "완료"
        or "목표 후보 목표" not in lines
        or "다음 " not in lines
        or "map1_01a->map2_02d" not in lines
        or "map2_02d->map2_18d" not in lines
        or "후보 진행" not in lines
        or "map=map2_18d" not in str(state.get("search") or "")
        or (not restored and "trialTransitions=routeAssist" not in str(state.get("search") or ""))
        or (restored and state.get("trialTransitions") != "routeAssist")
    ):
        raise WebDriverError(f"candidate route progress chain state is incomplete: {state!r}")
    if restored:
        if state.get("loaded") is not True:
            raise WebDriverError(f"candidate route progress chain restore did not load: {state!r}")
        return

    record = state.get("progressRecord") or {}
    detail = record.get("detail") or {}
    saved_progress = state.get("savedProgress") or {}
    saved_counts = saved_progress.get("counts") or {}
    before = state.get("before") or {}
    verify_route_candidate_auto_save(
        state.get("autoSave") or {},
        "map2_02d",
        "map2_18d",
        2,
        {"x": 46, "y": 0},
        {"x": 47, "y": 47},
    )
    verify_route_progress_feedback(
        state,
        "map2_18d",
        "map2_02d",
        "map2_18d",
        "후보 완료 2/2 map2_18d",
        2,
        2,
        expected_trigger="movement",
        expected_candidate_kind="map-exit",
        expected_route_auto_save=False,
        expected_direct_target_url=False,
    )
    if (
        state.get("handled") is not True
        or state.get("saved") is not True
        or before.get("activated") is not True
        or before.get("map") != "map2_02d"
        or before.get("target") != "map2_18d"
        or before.get("frontierValue") != "map2_18d:46,0"
        or "map2_18d" not in (before.get("targets") or [])
        or record.get("kind") != "route-candidate"
        or record.get("id") != "map2_02d->map2_18d"
        or record.get("map") != "map2_02d"
        or record.get("originalStoryFlagRuntimeImplemented") is not False
        or detail.get("sourceMap") != "map2_02d"
        or detail.get("targetMap") != "map2_18d"
        or detail.get("trigger") != "movement"
        or detail.get("candidateKind") != "map-exit"
        or detail.get("side") != "top"
        or detail.get("autoTrigger") is not True
        or detail.get("originalRoutePromotionImplemented") is not False
        or saved_counts.get("route-candidate") != 2
        or state.get("payloadMap") != "map2_18d"
    ):
        raise WebDriverError(f"candidate route progress chain record is incomplete: {state!r}")


def verify_route_title_continue_state(state: dict) -> None:
    verify_route_chain_state(state, restored=True)
    tile = state.get("tile") or {}
    play_hud = " ".join(str(line) for line in (state.get("playHudLines") or []))
    if (
        state.get("titleContinue") is not True
        or state.get("quickLoadHidden") is not False
        or state.get("quickLoadText") != "임시 불러오기"
        or state.get("routePathValue") != "map2_18d"
        or tile.get("x") != 47
        or tile.get("y") != 47
        or not has_restored_route_query(str(state.get("search") or ""), "map2_18d", "47,47")
        or "후보 map2_18d" not in play_hud
        or "진행 2" not in play_hud
        or "저장 있음" not in play_hud
    ):
        raise WebDriverError(f"candidate route title continue state is incomplete: {state!r}")


def write_report(report: dict) -> None:
    out_dir = ROOT / "out"
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "candidate_route_progress_browser_smoke.json").write_text(
        json.dumps(report, ensure_ascii=False, separators=(",", ":")) + "\n",
        encoding="utf-8",
    )
    lines = [
        "# Candidate Route Progress Browser Smoke",
        "",
        f"- status: `{report.get('status')}`",
        f"- route menu start: `{report.get('routeMenuStart')}`",
        f"- route goal menu: `{report.get('routeGoalMenu')}`",
        f"- route next objective action: `{report.get('routeNextObjectiveAction')}`",
        f"- route continuation objective action: `{report.get('routeContinuationObjectiveAction')}`",
        f"- route field encounter objective action: `{report.get('routeFieldEncounterObjectiveAction')}`",
        f"- quick objective keyboard: `{report.get('quickObjectiveKeyboard')}`",
        f"- quick objective toolbar next: `{report.get('quickObjectiveToolbarNext')}`",
        f"- quick objective button next: `{report.get('quickObjectiveButtonNext')}`",
        f"- quick objective button: `{report.get('quickObjectiveButton')}`",
        f"- quick objective toolbar: `{report.get('quickObjectiveToolbar')}`",
        f"- route next continuation button: `{report.get('routeNextContinuationButton')}`",
        f"- route continuation menu: `{report.get('routeContinuationMenu')}`",
        f"- route continuation entry: `{report.get('routeContinuationEntry')}`",
        f"- route continuation chain menu: `{report.get('routeContinuationChainMenu')}`",
        f"- route continuation chain entry: `{report.get('routeContinuationChainEntry')}`",
        f"- route continuation title restore: `{report.get('routeContinuationTitleRestore')}`",
        f"- route continuation restored menu: `{report.get('routeContinuationRestoredMenu')}`",
        f"- route continuation restored entry: `{report.get('routeContinuationRestoredEntry')}`",
        f"- route continuation restored title restore: `{report.get('routeContinuationRestoredTitleRestore')}`",
        f"- route continuation final menu: `{report.get('routeContinuationFinalMenu')}`",
        f"- route continuation final entry: `{report.get('routeContinuationFinalEntry')}`",
        f"- route continuation final title restore: `{report.get('routeContinuationFinalTitleRestore')}`",
        f"- route continuation final field encounter: `{report.get('routeContinuationFinalFieldEncounter')}`",
        f"- route continuation final field encounter title restore: `{report.get('routeContinuationFinalFieldEncounterTitleRestore')}`",
        f"- route completion notice: `{report.get('routeCompletionNotice')}`",
        f"- completion gate title restore: `{report.get('completionGateTitleRestore')}`",
        f"- restored completion menu notice: `{report.get('restoredCompletionMenuNotice')}`",
        f"- title route completion gate: `{report.get('titleRouteCompletionGate')}`",
        f"- title route clear gate: `{report.get('titleRouteClearGate')}`",
        f"- route clear gate title restore: `{report.get('routeClearGateTitleRestore')}`",
        f"- route clear controls: `{report.get('routeClearControls')}`",
        f"- restored route clear menu summary: `{report.get('restoredRouteClearMenuSummary')}`",
        f"- default map exit: `{report.get('defaultMapExit')}`",
        f"- recorded route: `{report.get('recordedRoute')}`",
        f"- source prompt: `{report.get('sourcePrompt')}`",
        f"- restored route: `{report.get('restoredRoute')}`",
        f"- chained route: `{report.get('chainedRoute')}`",
        f"- chained restore: `{report.get('chainedRestore')}`",
        f"- title restored route: `{report.get('titleRestoredRoute')}`",
        "",
    ]
    (out_dir / "candidate_route_progress_browser_smoke.md").write_text("\n".join(lines), encoding="utf-8")


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_route_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,
            )

            default_map_exit_url = load_map(
                base,
                port,
                session_id,
                {"map": "map1_01a", "startTile": "18,0", "moveMs": "80"},
            )
            execute_js(port, session_id, default_prototype_map_exit_script(), timeout=3)
            default_map_exit_state = wait_for_default_prototype_map_exit_state(port, session_id)
            verify_default_prototype_map_exit_state(default_map_exit_state)

            start_url = load_map(
                base,
                port,
                session_id,
                {"map": "map1_01a", "trialTransitions": "routeAssist", "startTile": "18,0", "moveMs": "80"},
            )
            execute_js(port, session_id, start_route_progress_script(), timeout=3)
            progress_state = wait_for_route_progress_state(port, session_id)
            verify_route_progress_state(progress_state)

            menu_start_url = load_map(
                base,
                port,
                session_id,
                {"map": "map1_02b", "startTile": "11,12"},
            )
            execute_js(port, session_id, route_menu_start_script(), timeout=3)
            menu_start_state = wait_for_route_menu_start_state(port, session_id)
            verify_route_menu_start_state(menu_start_state)

            route_goal_url = load_map(
                base,
                port,
                session_id,
                {"map": "map1_02b", "startTile": "11,12"},
            )
            execute_js(port, session_id, route_goal_menu_script(), timeout=3)
            route_goal_state = wait_for_route_goal_menu_state(port, session_id)
            verify_route_goal_menu_state(route_goal_state)

            source_prompt_url = load_map(
                base,
                port,
                session_id,
                {"map": "map1_01a", "trialTransitions": "routeAssist", "startTile": "18,0", "moveMs": "80"},
            )
            execute_js(port, session_id, route_source_prompt_script(), timeout=3)
            source_prompt_state = wait_for_route_source_prompt_state(port, session_id)
            verify_route_source_prompt_state(source_prompt_state)

            restore_url = load_map(base, port, session_id, {"map": "map1_01a", "startTile": "18,0"})
            execute_js(port, session_id, restore_route_progress_script(), timeout=3)
            restore_state = wait_for_route_restore_state(port, session_id)
            verify_route_progress_state(restore_state, restored=True)

            execute_js(port, session_id, continue_route_progress_script(), timeout=3)
            chain_state = wait_for_route_chain_state(port, session_id)
            verify_route_chain_state(chain_state)

            chain_restore_url = load_map(base, port, session_id, {"map": "map1_01a", "startTile": "18,0"})
            execute_js(port, session_id, restore_route_progress_script(), timeout=3)
            chain_restore_state = wait_for_route_chain_restore_state(port, session_id)
            verify_route_chain_state(chain_restore_state, restored=True)

            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("이어하기 map2_18d 47,47" in str(label) for label in (continue_title.get("titleMenuLabels") or [])):
                raise WebDriverError(f"title continue row did not summarize route 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_route_map = wait_for_continued_route_map(port, session_id)
            execute_js(port, session_id, title_continue_route_restore_script(), timeout=3)
            title_continue_restore = wait_for_title_continue_route_restore_state(port, session_id)
            verify_route_title_continue_state(title_continue_restore)

            route_next_objective_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map1_02b",
                    "startTile": "11,12",
                    "trialTransitions": "routeAssist",
                    "routeGoal": "map2_02d",
                    "encounter": "1",
                },
            )
            execute_js(
                port,
                session_id,
                route_objective_action_script("__hwanseCandidateRouteNextObjectiveAction"),
                timeout=3,
            )
            route_next_objective_action_state = wait_for_route_objective_action_state(
                port,
                session_id,
                "__hwanseCandidateRouteNextObjectiveAction",
                "route-next",
                "map1_01a",
            )
            verify_route_next_objective_action_state(route_next_objective_action_state)

            route_continuation_objective_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map2_02d",
                    "startTile": "47,47",
                    "trialTransitions": "routeAssist",
                    "routeGoal": "map2_02d",
                    "encounter": "1",
                },
            )
            execute_js(
                port,
                session_id,
                route_objective_action_script("__hwanseCandidateRouteContinuationObjectiveAction"),
                timeout=3,
            )
            route_continuation_objective_action_state = wait_for_route_objective_action_state(
                port,
                session_id,
                "__hwanseCandidateRouteContinuationObjectiveAction",
                "route-continuation",
                "map2_02d",
            )
            verify_route_continuation_objective_action_state(route_continuation_objective_action_state)

            route_field_objective_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map2_14j",
                    "startTile": "18,14",
                    "trialTransitions": "routeAssist",
                    "routeGoal": "map2_14j",
                },
            )
            execute_js(
                port,
                session_id,
                route_objective_action_script("__hwanseCandidateRouteFieldEncounterObjectiveAction"),
                timeout=3,
            )
            route_field_objective_action_state = wait_for_route_objective_action_state(
                port,
                session_id,
                "__hwanseCandidateRouteFieldEncounterObjectiveAction",
                "field-encounter-enable",
                "map2_14j",
            )
            verify_route_field_encounter_objective_action_state(route_field_objective_action_state)

            quick_objective_keyboard_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map1_02b",
                    "startTile": "11,12",
                    "trialTransitions": "routeAssist",
                    "routeGoal": "map2_02d",
                    "encounter": "1",
                },
            )
            execute_js(
                port,
                session_id,
                quick_objective_keyboard_script("__hwanseQuickObjectiveKeyboard"),
                timeout=3,
            )
            quick_objective_keyboard_state = wait_for_quick_objective_control_state(
                port,
                session_id,
                "__hwanseQuickObjectiveKeyboard",
                "keyboard",
                "route-next",
                "map1_01a",
            )
            verify_quick_objective_keyboard_state(quick_objective_keyboard_state)

            quick_objective_toolbar_next_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map1_02b",
                    "startTile": "11,12",
                    "trialTransitions": "routeAssist",
                    "routeGoal": "map2_02d",
                    "encounter": "1",
                },
            )
            execute_js(
                port,
                session_id,
                quick_objective_toolbar_script("__hwanseQuickObjectiveToolbarNext"),
                timeout=3,
            )
            quick_objective_toolbar_next_state = wait_for_quick_objective_control_state(
                port,
                session_id,
                "__hwanseQuickObjectiveToolbarNext",
                "objective-action-button",
                "route-next",
                "map1_01a",
            )
            verify_quick_objective_toolbar_next_state(quick_objective_toolbar_next_state)

            quick_objective_button_next_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map1_02b",
                    "startTile": "11,12",
                    "trialTransitions": "routeAssist",
                    "routeGoal": "map2_02d",
                    "encounter": "1",
                },
            )
            execute_js(
                port,
                session_id,
                quick_objective_button_script("__hwanseQuickObjectiveButtonNext"),
                timeout=3,
            )
            quick_objective_button_next_state = wait_for_quick_objective_control_state(
                port,
                session_id,
                "__hwanseQuickObjectiveButtonNext",
                "virtual-objective-button",
                "route-next",
                "map1_01a",
            )
            verify_quick_objective_button_next_state(quick_objective_button_next_state)

            quick_objective_button_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map2_14j",
                    "startTile": "18,14",
                    "trialTransitions": "routeAssist",
                    "routeGoal": "map2_14j",
                },
            )
            execute_js(
                port,
                session_id,
                quick_objective_button_script("__hwanseQuickObjectiveButton"),
                timeout=3,
            )
            quick_objective_button_state = wait_for_quick_objective_control_state(
                port,
                session_id,
                "__hwanseQuickObjectiveButton",
                "virtual-objective-button",
                "field-encounter-enable",
                "map2_14j",
            )
            verify_quick_objective_button_state(quick_objective_button_state)

            quick_objective_toolbar_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map2_02d",
                    "startTile": "47,47",
                    "trialTransitions": "routeAssist",
                    "routeGoal": "map2_02d",
                    "encounter": "1",
                },
            )
            execute_js(
                port,
                session_id,
                quick_objective_toolbar_script("__hwanseQuickObjectiveToolbar"),
                timeout=3,
            )
            quick_objective_toolbar_state = wait_for_quick_objective_control_state(
                port,
                session_id,
                "__hwanseQuickObjectiveToolbar",
                "objective-action-button",
                "route-continuation",
                "map2_02d",
            )
            verify_quick_objective_toolbar_state(quick_objective_toolbar_state)

            route_next_continuation_button_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map2_02d",
                    "startTile": "47,47",
                    "trialTransitions": "routeAssist",
                    "routeGoal": "map2_02d",
                    "encounter": "1",
                },
            )
            execute_js(port, session_id, route_next_continuation_button_script(), timeout=3)
            route_next_continuation_button_state = wait_for_route_next_continuation_button_state(
                port,
                session_id,
                "map2_02d",
                "map2_09g",
            )
            verify_route_next_continuation_button_state(
                route_next_continuation_button_state,
                "map2_02d",
                "map2_02d",
                "map2_09g",
            )

            continuation_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map2_02d",
                    "startTile": "47,47",
                    "trialTransitions": "routeAssist",
                    "routeGoal": "map2_02d",
                    "encounter": "1",
                },
            )
            execute_js(port, session_id, route_continuation_menu_script(), timeout=3)
            continuation_state = wait_for_route_continuation_menu_state(
                port,
                session_id,
                "map2_02d",
                "map2_02d",
                "map2_09g",
            )
            verify_route_continuation_menu_state(continuation_state, "map2_02d", "map2_02d", "map2_09g")
            execute_js(port, session_id, route_continuation_entry_script(), timeout=3)
            continuation_entry_state = wait_for_route_continuation_entry_state(
                port,
                session_id,
                "map2_02d",
                "map2_09g",
                1,
            )
            verify_route_continuation_entry_state(continuation_entry_state, "map2_02d", "map2_09g", 1, "map2_10g")

            execute_js(port, session_id, route_continuation_menu_script(), timeout=3)
            continuation_chain_state = wait_for_route_continuation_menu_state(
                port,
                session_id,
                "map2_09g",
                "map2_09g",
                "map2_10g",
            )
            verify_route_continuation_menu_state(continuation_chain_state, "map2_09g", "map2_09g", "map2_10g")
            execute_js(port, session_id, route_continuation_entry_script(), timeout=3)
            continuation_chain_entry_state = wait_for_route_continuation_entry_state(
                port,
                session_id,
                "map2_09g",
                "map2_10g",
                2,
            )
            verify_route_continuation_entry_state(continuation_chain_entry_state, "map2_09g", "map2_10g", 2, "map2_12h")

            continuation_title_url = title_url
            request_json(port, "POST", f"/session/{session_id}/url", {"url": continuation_title_url}, timeout=30)
            wait_for_page(port, session_id)
            continuation_continue_title = wait_for_continue_ready_title(port, session_id)
            continuation_title_label = next(
                (
                    str(label)
                    for label in (continuation_continue_title.get("titleMenuLabels") or [])
                    if str(label).startswith("이어하기")
                ),
                "",
            )
            if "이어하기 map2_10g" not in continuation_title_label:
                raise WebDriverError(f"title continue row did not summarize continuation quick save: {continuation_continue_title!r}")
            continuation_title_click = execute_js(port, session_id, click_title_continue_script(), timeout=3)
            if not isinstance(continuation_title_click, dict) or continuation_title_click.get("ok") is not True:
                raise WebDriverError(f"continuation title continue button was not usable: {continuation_title_click!r}")
            wait_for_page(port, session_id)
            wait_for_map_runtime(port, session_id, "map2_10g")
            execute_js(port, session_id, route_continuation_title_restore_script(), timeout=3)
            continuation_title_restore = wait_for_route_continuation_title_restore_state(
                port,
                session_id,
                "map2_10g",
                "map2_12h",
                2,
            )
            verify_route_continuation_title_restore_state(
                continuation_title_restore,
                continuation_title_label,
                "map2_10g",
                {"x": 25, "y": 12},
                "map2_12h",
                2,
            )

            execute_js(port, session_id, route_continuation_menu_script(), timeout=3)
            continuation_restored_state = wait_for_route_continuation_menu_state(
                port,
                session_id,
                "map2_10g",
                "map2_10g",
                "map2_12h",
            )
            verify_route_continuation_menu_state(continuation_restored_state, "map2_10g", "map2_10g", "map2_12h")
            execute_js(port, session_id, route_continuation_entry_script(), timeout=3)
            continuation_restored_entry_state = wait_for_route_continuation_entry_state(
                port,
                session_id,
                "map2_10g",
                "map2_12h",
                3,
            )
            verify_route_continuation_entry_state(
                continuation_restored_entry_state,
                "map2_10g",
                "map2_12h",
                3,
                "map2_14j",
            )

            restored_title_url = title_url
            request_json(port, "POST", f"/session/{session_id}/url", {"url": restored_title_url}, timeout=30)
            wait_for_page(port, session_id)
            restored_continue_title = wait_for_continue_ready_title(port, session_id)
            restored_title_label = next(
                (
                    str(label)
                    for label in (restored_continue_title.get("titleMenuLabels") or [])
                    if str(label).startswith("이어하기")
                ),
                "",
            )
            if "이어하기 map2_12h" not in restored_title_label:
                raise WebDriverError(f"title continue row did not summarize restored continuation quick save: {restored_continue_title!r}")
            restored_title_click = execute_js(port, session_id, click_title_continue_script(), timeout=3)
            if not isinstance(restored_title_click, dict) or restored_title_click.get("ok") is not True:
                raise WebDriverError(f"restored continuation title continue button was not usable: {restored_title_click!r}")
            wait_for_page(port, session_id)
            wait_for_map_runtime(port, session_id, "map2_12h")
            execute_js(port, session_id, route_continuation_title_restore_script(), timeout=3)
            restored_title_restore = wait_for_route_continuation_title_restore_state(
                port,
                session_id,
                "map2_12h",
                "map2_14j",
                3,
            )
            verify_route_continuation_title_restore_state(
                restored_title_restore,
                restored_title_label,
                "map2_12h",
                {"x": 26, "y": 19},
                "map2_14j",
                3,
            )

            execute_js(port, session_id, route_continuation_menu_script(), timeout=3)
            continuation_final_state = wait_for_route_continuation_menu_state(
                port,
                session_id,
                "map2_12h",
                "map2_12h",
                "map2_14j",
            )
            verify_route_continuation_menu_state(continuation_final_state, "map2_12h", "map2_12h", "map2_14j")
            execute_js(port, session_id, route_continuation_entry_script(), timeout=3)
            continuation_final_entry_state = wait_for_route_continuation_entry_state(
                port,
                session_id,
                "map2_12h",
                "map2_14j",
                4,
            )
            verify_route_continuation_entry_state(
                continuation_final_entry_state,
                "map2_12h",
                "map2_14j",
                4,
            )

            final_title_url = title_url
            request_json(port, "POST", f"/session/{session_id}/url", {"url": final_title_url}, timeout=30)
            wait_for_page(port, session_id)
            final_continue_title = wait_for_continue_ready_title(port, session_id)
            final_title_label = next(
                (
                    str(label)
                    for label in (final_continue_title.get("titleMenuLabels") or [])
                    if str(label).startswith("이어하기")
                ),
                "",
            )
            if "이어하기 map2_14j" not in final_title_label:
                raise WebDriverError(f"title continue row did not summarize final continuation quick save: {final_continue_title!r}")
            final_title_click = execute_js(port, session_id, click_title_continue_script(), timeout=3)
            if not isinstance(final_title_click, dict) or final_title_click.get("ok") is not True:
                raise WebDriverError(f"final continuation title continue button was not usable: {final_title_click!r}")
            wait_for_page(port, session_id)
            wait_for_map_runtime(port, session_id, "map2_14j")
            execute_js(port, session_id, route_continuation_title_restore_script(), timeout=3)
            final_title_restore = wait_for_route_continuation_title_restore_state(
                port,
                session_id,
                "map2_14j",
                None,
                4,
            )
            final_entry_tile = continuation_final_entry_state.get("tile") or {}
            verify_route_continuation_title_restore_state(
                final_title_restore,
                final_title_label,
                "map2_14j",
                {"x": int(final_entry_tile.get("x") or 0), "y": int(final_entry_tile.get("y") or 0)},
                None,
                4,
            )

            execute_js(port, session_id, route_continuation_field_encounter_script(), timeout=3)
            continuation_final_field_encounter = wait_for_route_continuation_field_encounter(
                port,
                session_id,
                "map2_14j",
                4,
            )

            field_encounter_title_url = title_url
            request_json(port, "POST", f"/session/{session_id}/url", {"url": field_encounter_title_url}, timeout=30)
            wait_for_page(port, session_id)
            field_encounter_continue_title = wait_for_continue_ready_title(port, session_id)
            field_encounter_title_label = next(
                (
                    str(label)
                    for label in (field_encounter_continue_title.get("titleMenuLabels") or [])
                    if str(label).startswith("이어하기")
                ),
                "",
            )
            if "이어하기 map2_14j" not in field_encounter_title_label or "전투 후보 map2_14j" not in field_encounter_title_label:
                raise WebDriverError(
                    f"title continue row did not summarize final field encounter quick save: {field_encounter_continue_title!r}"
                )
            field_encounter_title_click = execute_js(port, session_id, click_title_continue_script(), timeout=3)
            if not isinstance(field_encounter_title_click, dict) or field_encounter_title_click.get("ok") is not True:
                raise WebDriverError(f"final field encounter title continue button was not usable: {field_encounter_title_click!r}")
            wait_for_page(port, session_id)
            wait_for_map_runtime(port, session_id, "map2_14j")
            execute_js(port, session_id, route_continuation_title_restore_script(), timeout=3)
            field_encounter_title_restore = wait_for_route_continuation_title_restore_state(
                port,
                session_id,
                "map2_14j",
                None,
                4,
            )
            field_encounter_tile = continuation_final_field_encounter.get("foot") or {}
            verify_route_continuation_field_encounter_title_restore_state(
                field_encounter_title_restore,
                field_encounter_title_label,
                "map2_14j",
                {"x": int(field_encounter_tile.get("x") or 0), "y": int(field_encounter_tile.get("y") or 0)},
                4,
            )
            execute_js(port, session_id, route_completion_notice_script(), timeout=3)
            route_completion_notice = wait_for_route_completion_notice(port, session_id)

            completion_gate_title_url = title_url
            request_json(port, "POST", f"/session/{session_id}/url", {"url": completion_gate_title_url}, timeout=30)
            wait_for_page(port, session_id)
            completion_gate_continue_title = wait_for_continue_ready_title(port, session_id)
            completion_gate_title_label = next(
                (
                    str(label)
                    for label in (completion_gate_continue_title.get("titleMenuLabels") or [])
                    if str(label).startswith("이어하기")
                ),
                "",
            )
            if (
                "이어하기 map2_14j" not in completion_gate_title_label
                or "전투 후보 map2_14j" not in completion_gate_title_label
                or "완료 map2_14j" not in completion_gate_title_label
            ):
                raise WebDriverError(
                    f"title continue row did not summarize route completion gate save: {completion_gate_continue_title!r}"
                )
            completion_gate_title_click = execute_js(port, session_id, click_title_continue_script(), timeout=3)
            if not isinstance(completion_gate_title_click, dict) or completion_gate_title_click.get("ok") is not True:
                raise WebDriverError(
                    f"route completion gate title continue button was not usable: {completion_gate_title_click!r}"
                )
            wait_for_page(port, session_id)
            wait_for_map_runtime(port, session_id, "map2_14j")
            execute_js(port, session_id, route_continuation_title_restore_script(), timeout=3)
            completion_gate_title_restore = wait_for_route_continuation_title_restore_state(
                port,
                session_id,
                "map2_14j",
                None,
                4,
            )
            completion_gate_tile = (route_completion_notice.get("before") or {}).get("tile") or {}
            verify_route_completion_gate_title_restore_state(
                completion_gate_title_restore,
                completion_gate_title_label,
                "map2_14j",
                {"x": int(completion_gate_tile.get("x") or 0), "y": int(completion_gate_tile.get("y") or 0)},
                4,
            )
            execute_js(port, session_id, restored_route_completion_menu_notice_script(), timeout=3)
            restored_completion_menu_notice = wait_for_restored_route_completion_menu_notice(port, session_id)

            title_route_completion_gate_url = title_url
            request_json(port, "POST", f"/session/{session_id}/url", {"url": title_route_completion_gate_url}, timeout=30)
            wait_for_page(port, session_id)
            title_route_completion_gate_title = wait_for_continue_ready_title(port, session_id)
            if (
                "routeCompletion" not in (title_route_completion_gate_title.get("titleMenuKeys") or [])
                or "후보 완료 map2_14j" not in (title_route_completion_gate_title.get("titleMenuLabels") or [])
            ):
                raise WebDriverError(
                    f"title route completion gate item was not exposed: {title_route_completion_gate_title!r}"
            )
            execute_js(port, session_id, click_title_route_completion_gate_script(), timeout=3)
            title_route_completion_gate = wait_for_title_route_completion_gate(port, session_id)

            title_route_clear_gate_url = title_url
            request_json(port, "POST", f"/session/{session_id}/url", {"url": title_route_clear_gate_url}, timeout=30)
            wait_for_page(port, session_id)
            title_route_clear_gate_title = wait_for_continue_ready_title(port, session_id)
            if (
                "routeClear" not in (title_route_clear_gate_title.get("titleMenuKeys") or [])
                or "후보 클리어 저장 map2_14j" not in (title_route_clear_gate_title.get("titleMenuLabels") or [])
            ):
                raise WebDriverError(
                    f"title route clear gate item was not exposed: {title_route_clear_gate_title!r}"
            )
            execute_js(port, session_id, click_title_route_clear_gate_script(), timeout=3)
            title_route_clear_gate = wait_for_title_route_clear_gate(port, session_id)

            route_clear_gate_title_restore_url = title_url
            request_json(port, "POST", f"/session/{session_id}/url", {"url": route_clear_gate_title_restore_url}, timeout=30)
            wait_for_page(port, session_id)
            route_clear_gate_continue_title = wait_for_continue_ready_title(port, session_id)
            route_clear_gate_continue_keys = route_clear_gate_continue_title.get("titleMenuKeys") or []
            route_clear_gate_continue_labels = route_clear_gate_continue_title.get("titleMenuLabels") or []
            route_clear_gate_title_label = next(
                (
                    str(label)
                    for label in route_clear_gate_continue_labels
                    if str(label).startswith("이어하기")
                ),
                "",
            )
            if (
                "이어하기 map2_14j" not in route_clear_gate_title_label
                or "클리어 map2_14j" not in route_clear_gate_title_label
                or "완료 map2_14j" in route_clear_gate_title_label
                or "routeCompletion" in route_clear_gate_continue_keys
                or "후보 완료 map2_14j" in route_clear_gate_continue_labels
                or "routeClear" not in route_clear_gate_continue_keys
                or "후보 클리어 map2_14j" not in route_clear_gate_continue_labels
            ):
                raise WebDriverError(
                    f"title continue row did not summarize route clear gate save: {route_clear_gate_continue_title!r}"
                )
            route_clear_gate_title_click = execute_js(port, session_id, click_title_continue_script(), timeout=3)
            if not isinstance(route_clear_gate_title_click, dict) or route_clear_gate_title_click.get("ok") is not True:
                raise WebDriverError(
                    f"route clear gate title continue button was not usable: {route_clear_gate_title_click!r}"
                )
            wait_for_page(port, session_id)
            wait_for_map_runtime(port, session_id, "map2_14j")
            execute_js(port, session_id, route_continuation_title_restore_script(), timeout=3)
            route_clear_gate_title_restore = wait_for_route_continuation_title_restore_state(
                port,
                session_id,
                "map2_14j",
                None,
                4,
            )
            verify_route_clear_gate_title_restore_state(
                route_clear_gate_title_restore,
                route_clear_gate_title_label,
                "map2_14j",
                {"x": int(completion_gate_tile.get("x") or 0), "y": int(completion_gate_tile.get("y") or 0)},
                4,
            )
            execute_js(port, session_id, route_clear_controls_script(), timeout=3)
            route_clear_controls = wait_for_route_clear_controls(port, session_id)
            execute_js(port, session_id, restored_route_clear_menu_summary_script(), timeout=3)
            restored_route_clear_menu_summary = wait_for_restored_route_clear_menu_summary(port, session_id)

            title_continue_label = next(
                (str(label) for label in (continue_title.get("titleMenuLabels") or []) if str(label).startswith("이어하기")),
                "",
            )

            def progress_count(state: dict, kind: str) -> int | None:
                return ((state.get("progress") or {}).get("counts") or {}).get(kind)

            def saved_progress_count(state: dict, kind: str) -> int | None:
                return ((state.get("savedProgress") or {}).get("counts") or {}).get(kind)

            def route_candidate_count(state: dict) -> int | None:
                return progress_count(state, "route-candidate")

            def saved_route_candidate_count(state: dict) -> int | None:
                return saved_progress_count(state, "route-candidate")

            def route_completion(state: dict) -> dict:
                return ((state.get("completion") or {}).get("route") or {})

            def route_assist_progress_event(state: dict) -> dict:
                return ((state.get("routeAssistAutoSave") or {}).get("progressEvent") or {})

            def route_assist_progress_detail(state: dict) -> dict:
                return route_assist_progress_event(state).get("detail") or {}

            field_encounter_movement = continuation_final_field_encounter.get("movement") or {}
            field_encounter_before_foot = field_encounter_movement.get("beforeFoot") or {}
            field_encounter_after_foot = field_encounter_movement.get("afterFoot") or {}
            field_encounter_battle = (
                (continuation_final_field_encounter.get("startSnapshot") or {}).get("battleCandidate") or {}
            )
            field_encounter_auto_save = continuation_final_field_encounter.get("victoryAutoSave") or {}
            field_encounter_auto_tile = field_encounter_auto_save.get("payloadTile") or {}

            report = {
                "status": "passed",
                "base": base,
                "defaultMapExitUrl": default_map_exit_url,
                "startUrl": start_url,
                "menuStartUrl": menu_start_url,
                "routeGoalUrl": route_goal_url,
                "routeNextObjectiveUrl": route_next_objective_url,
                "routeContinuationObjectiveUrl": route_continuation_objective_url,
                "routeFieldObjectiveUrl": route_field_objective_url,
                "quickObjectiveKeyboardUrl": quick_objective_keyboard_url,
                "quickObjectiveToolbarNextUrl": quick_objective_toolbar_next_url,
                "quickObjectiveButtonNextUrl": quick_objective_button_next_url,
                "quickObjectiveButtonUrl": quick_objective_button_url,
                "quickObjectiveToolbarUrl": quick_objective_toolbar_url,
                "sourcePromptUrl": source_prompt_url,
                "restoreUrl": restore_url,
                "chainRestoreUrl": chain_restore_url,
                "titleUrl": title_url,
                "routeNextContinuationButtonUrl": route_next_continuation_button_url,
                "continuationUrl": continuation_url,
                "continuationTitleUrl": continuation_title_url,
                "continueTitle": continue_title,
                "titleContinueClick": title_continue_click,
                "continuedRouteMap": continued_route_map,
                "continuationContinueTitle": continuation_continue_title,
                "continuationTitleContinueClick": continuation_title_click,
                "restoredContinueTitle": restored_continue_title,
                "restoredTitleContinueClick": restored_title_click,
                "finalContinueTitle": final_continue_title,
                "finalTitleContinueClick": final_title_click,
                "fieldEncounterContinueTitle": field_encounter_continue_title,
                "fieldEncounterTitleContinueClick": field_encounter_title_click,
                "completionGateTitleUrl": completion_gate_title_url,
                "completionGateContinueTitle": completion_gate_continue_title,
                "completionGateTitleContinueClick": completion_gate_title_click,
                "titleRouteCompletionGateUrl": title_route_completion_gate_url,
                "titleRouteCompletionGateTitle": title_route_completion_gate_title,
                "titleRouteClearGateUrl": title_route_clear_gate_url,
                "titleRouteClearGateTitle": title_route_clear_gate_title,
                "routeClearGateTitleRestoreUrl": route_clear_gate_title_restore_url,
                "routeClearGateContinueTitle": route_clear_gate_continue_title,
                "routeClearGateTitleContinueClick": route_clear_gate_title_click,
                "routeMenuStart": (
                    "map1_02b menu "
                    f"startItem={(menu_start_state.get('startItem') or {}).get('name')} "
                    f"nextItem={(menu_start_state.get('nextItem') or {}).get('name')} "
                    f"routeGoal={menu_start_state.get('selectedRouteGoal')} "
                    f"hud={'|'.join(str(line) for line in menu_start_state.get('playHudLines') or [])} "
                    f"trialTransitions={menu_start_state.get('trialTransitions')} "
                    "runtimeMenuRouteAssist=True"
                ),
                "routeGoalMenu": (
                    "map1_02b goalMenu "
                    f"item={(route_goal_state.get('goalMenuItem') or {}).get('name')} "
                    f"target={(route_goal_state.get('targetItem') or {}).get('routeTarget')} "
                    f"nextItem={(route_goal_state.get('nextItem') or {}).get('name')} "
                    f"routeGoal={route_goal_state.get('selectedRouteGoal')} "
                    f"routePathValue={route_goal_state.get('routePathValue')} "
                    f"hud={'|'.join(str(line) for line in route_goal_state.get('playHudLines') or [])} "
                    f"trialTransitions={route_goal_state.get('trialTransitions')} "
                    "runtimeMenuRouteGoal=True"
                ),
                "routeNextObjectiveAction": (
                    "map1_02b objectiveAction "
                    f"action={(route_next_objective_action_state.get('objectiveAction') or {}).get('action')} "
                    f"handled={(route_next_objective_action_state.get('objectiveAction') or {}).get('handled')} "
                    f"result={route_next_objective_action_state.get('objectiveActionResult')} "
                    f"objective={(route_next_objective_action_state.get('objectiveAction') or {}).get('objective', {}).get('title')} "
                    f"next={(route_next_objective_action_state.get('objectiveAction') or {}).get('objective', {}).get('nextAction')} "
                    f"beforeMap={route_next_objective_action_state.get('beforeMap')} "
                    f"map={route_next_objective_action_state.get('map')} "
                    f"routeTarget={(route_next_objective_action_state.get('objectiveAction') or {}).get('routeTarget')} "
                    f"routeGoal={route_next_objective_action_state.get('selectedRouteGoal')} "
                    f"routePathValue={route_next_objective_action_state.get('routePathValue')} "
                    f"routeStep={route_next_objective_action_state.get('routeStepSourceParam')}->{route_next_objective_action_state.get('routeStepTargetParam')} "
                    f"routeAutoSave={route_next_objective_action_state.get('routeAutoSaveParam')} "
                    f"hud={'|'.join(str(line) for line in route_next_objective_action_state.get('playHudLines') or [])} "
                    "runtimeObjectiveRouteNext=True"
                ),
                "routeContinuationObjectiveAction": (
                    "map2_02d objectiveAction "
                    f"action={(route_continuation_objective_action_state.get('objectiveAction') or {}).get('action')} "
                    f"handled={(route_continuation_objective_action_state.get('objectiveAction') or {}).get('handled')} "
                    f"result={route_continuation_objective_action_state.get('objectiveActionResult')} "
                    f"objective={(route_continuation_objective_action_state.get('objectiveAction') or {}).get('objective', {}).get('title')} "
                    f"next={(route_continuation_objective_action_state.get('objectiveAction') or {}).get('objective', {}).get('nextAction')} "
                    f"beforeMap={route_continuation_objective_action_state.get('beforeMap')} "
                    f"map={route_continuation_objective_action_state.get('map')} "
                    f"routeTarget={(route_continuation_objective_action_state.get('objectiveAction') or {}).get('routeTarget')} "
                    f"routeGoal={route_continuation_objective_action_state.get('selectedRouteGoal')} "
                    f"routePathValue={route_continuation_objective_action_state.get('routePathValue')} "
                    f"transitionTarget={route_continuation_objective_action_state.get('transitionTargetParam')} "
                    f"routeNextText={route_continuation_objective_action_state.get('routeNextText')} "
                    f"routeNextTitle={route_continuation_objective_action_state.get('routeNextTitle')} "
                    f"hud={'|'.join(str(line) for line in route_continuation_objective_action_state.get('playHudLines') or [])} "
                    "runtimeObjectiveContinuation=True"
                ),
                "routeFieldEncounterObjectiveAction": (
                    "map2_14j objectiveAction "
                    f"action={(route_field_objective_action_state.get('objectiveAction') or {}).get('action')} "
                    f"handled={(route_field_objective_action_state.get('objectiveAction') or {}).get('handled')} "
                    f"result={route_field_objective_action_state.get('objectiveActionResult')} "
                    f"objective={(route_field_objective_action_state.get('objectiveAction') or {}).get('objective', {}).get('title')} "
                    f"next={(route_field_objective_action_state.get('objectiveAction') or {}).get('objective', {}).get('nextAction')} "
                    f"map={route_field_objective_action_state.get('map')} "
                    f"routeTarget={(route_field_objective_action_state.get('objectiveAction') or {}).get('routeTarget')} "
                    f"fieldEnabled={(route_field_objective_action_state.get('fieldEncounter') or {}).get('enabled')} "
                    f"fieldStep={(route_field_objective_action_state.get('fieldEncounter') or {}).get('stepCount')}/"
                    f"{(route_field_objective_action_state.get('fieldEncounter') or {}).get('threshold')} "
                    f"fieldLastMap={(route_field_objective_action_state.get('fieldEncounter') or {}).get('lastMap')} "
                    f"menuLabel={route_field_objective_action_state.get('fieldEncounterMenuLabel')} "
                    f"autoSaved={(route_field_objective_action_state.get('fieldEncounterModeAutoSave') or {}).get('saved')} "
                    f"autoSource={(route_field_objective_action_state.get('fieldEncounterModeAutoSave') or {}).get('source')} "
                    f"payloadMap={(route_field_objective_action_state.get('fieldEncounterModeAutoSave') or {}).get('payloadMap')} "
                    f"modeSource={(route_field_objective_action_state.get('fieldEncounterMode') or {}).get('source')} "
                    f"savedPayloadMap={route_field_objective_action_state.get('savedPayloadMap')} "
                    f"hud={'|'.join(str(line) for line in route_field_objective_action_state.get('playHudLines') or [])} "
                    "runtimeObjectiveFieldEncounter=True"
                ),
                "quickObjectiveKeyboard": (
                    "map1_02b quickObjectiveKeyboard "
                    f"source={(quick_objective_keyboard_state.get('quickAction') or {}).get('source')} "
                    f"action={(quick_objective_keyboard_state.get('quickAction') or {}).get('action')} "
                    f"handled={(quick_objective_keyboard_state.get('quickAction') or {}).get('handled')} "
                    f"beforeButtonHidden={quick_objective_keyboard_state.get('beforeButtonHidden')} "
                    f"beforeButtonTitle={quick_objective_keyboard_state.get('beforeButtonTitle')} "
                    f"map={quick_objective_keyboard_state.get('map')} "
                    f"routeGoal={quick_objective_keyboard_state.get('selectedRouteGoal')} "
                    f"routeAutoSave={quick_objective_keyboard_state.get('routeAutoSaveParam')} "
                    f"savedPayloadMap={quick_objective_keyboard_state.get('savedPayloadMap')} "
                    f"quickObjectiveSound={((quick_objective_keyboard_state.get('quickAction') or {}).get('sound') or {}).get('source')}:"
                    f"{((quick_objective_keyboard_state.get('quickAction') or {}).get('sound') or {}).get('inputSource')}:"
                    f"{((quick_objective_keyboard_state.get('quickAction') or {}).get('sound') or {}).get('action')} "
                    f"quickObjectiveSoundSrc={((quick_objective_keyboard_state.get('quickAction') or {}).get('sound') or {}).get('soundSrc')} "
                    f"quickObjectiveSoundPlayed={((quick_objective_keyboard_state.get('quickAction') or {}).get('sound') or {}).get('soundPlayed')} "
                    f"menuConfirmSoundCount={quick_objective_keyboard_state.get('menuConfirmSoundCount')} "
                    f"hud={'|'.join(str(line) for line in quick_objective_keyboard_state.get('playHudLines') or [])} "
                    "quickObjectiveKeyboard=True"
                ),
                "quickObjectiveToolbarNext": (
                    "map1_02b quickObjectiveToolbarNext "
                    f"source={(quick_objective_toolbar_next_state.get('quickAction') or {}).get('source')} "
                    f"action={(quick_objective_toolbar_next_state.get('quickAction') or {}).get('action')} "
                    f"handled={(quick_objective_toolbar_next_state.get('quickAction') or {}).get('handled')} "
                    f"beforeObjectiveButtonHidden={quick_objective_toolbar_next_state.get('beforeObjectiveButtonHidden')} "
                    f"beforeObjectiveButtonDisabled={quick_objective_toolbar_next_state.get('beforeObjectiveButtonDisabled')} "
                    f"beforeObjectiveButtonText={quick_objective_toolbar_next_state.get('beforeObjectiveButtonText')} "
                    f"beforeObjectiveButtonTitle={quick_objective_toolbar_next_state.get('beforeObjectiveButtonTitle')} "
                    f"map={quick_objective_toolbar_next_state.get('map')} "
                    f"routeGoal={quick_objective_toolbar_next_state.get('selectedRouteGoal')} "
                    f"routeAutoSave={quick_objective_toolbar_next_state.get('routeAutoSaveParam')} "
                    f"savedPayloadMap={quick_objective_toolbar_next_state.get('savedPayloadMap')} "
                    f"objectiveButtonHidden={quick_objective_toolbar_next_state.get('objectiveButtonHidden')} "
                    f"objectiveButtonTitle={quick_objective_toolbar_next_state.get('objectiveButtonTitle')} "
                    f"quickObjectiveSound={((quick_objective_toolbar_next_state.get('quickAction') or {}).get('sound') or {}).get('source')}:"
                    f"{((quick_objective_toolbar_next_state.get('quickAction') or {}).get('sound') or {}).get('inputSource')}:"
                    f"{((quick_objective_toolbar_next_state.get('quickAction') or {}).get('sound') or {}).get('action')} "
                    f"quickObjectiveSoundSrc={((quick_objective_toolbar_next_state.get('quickAction') or {}).get('sound') or {}).get('soundSrc')} "
                    f"quickObjectiveSoundPlayed={((quick_objective_toolbar_next_state.get('quickAction') or {}).get('sound') or {}).get('soundPlayed')} "
                    f"menuConfirmSoundCount={quick_objective_toolbar_next_state.get('menuConfirmSoundCount')} "
                    f"hud={'|'.join(str(line) for line in quick_objective_toolbar_next_state.get('playHudLines') or [])} "
                    "quickObjectiveToolbarNext=True"
                ),
                "quickObjectiveButtonNext": (
                    "map1_02b quickObjectiveButtonNext "
                    f"source={(quick_objective_button_next_state.get('quickAction') or {}).get('source')} "
                    f"action={(quick_objective_button_next_state.get('quickAction') or {}).get('action')} "
                    f"handled={(quick_objective_button_next_state.get('quickAction') or {}).get('handled')} "
                    f"beforeButtonHidden={quick_objective_button_next_state.get('beforeButtonHidden')} "
                    f"beforeButtonTitle={quick_objective_button_next_state.get('beforeButtonTitle')} "
                    f"map={quick_objective_button_next_state.get('map')} "
                    f"routeGoal={quick_objective_button_next_state.get('selectedRouteGoal')} "
                    f"routeAutoSave={quick_objective_button_next_state.get('routeAutoSaveParam')} "
                    f"savedPayloadMap={quick_objective_button_next_state.get('savedPayloadMap')} "
                    f"buttonHidden={quick_objective_button_next_state.get('buttonHidden')} "
                    f"buttonTitle={quick_objective_button_next_state.get('buttonTitle')} "
                    f"quickObjectiveSound={((quick_objective_button_next_state.get('quickAction') or {}).get('sound') or {}).get('source')}:"
                    f"{((quick_objective_button_next_state.get('quickAction') or {}).get('sound') or {}).get('inputSource')}:"
                    f"{((quick_objective_button_next_state.get('quickAction') or {}).get('sound') or {}).get('action')} "
                    f"quickObjectiveSoundSrc={((quick_objective_button_next_state.get('quickAction') or {}).get('sound') or {}).get('soundSrc')} "
                    f"quickObjectiveSoundPlayed={((quick_objective_button_next_state.get('quickAction') or {}).get('sound') or {}).get('soundPlayed')} "
                    f"menuConfirmSoundCount={quick_objective_button_next_state.get('menuConfirmSoundCount')} "
                    f"hud={'|'.join(str(line) for line in quick_objective_button_next_state.get('playHudLines') or [])} "
                    "quickObjectiveButtonNext=True"
                ),
                "quickObjectiveButton": (
                    "map2_14j quickObjectiveButton "
                    f"source={(quick_objective_button_state.get('quickAction') or {}).get('source')} "
                    f"action={(quick_objective_button_state.get('quickAction') or {}).get('action')} "
                    f"handled={(quick_objective_button_state.get('quickAction') or {}).get('handled')} "
                    f"beforeButtonHidden={quick_objective_button_state.get('beforeButtonHidden')} "
                    f"beforeButtonTitle={quick_objective_button_state.get('beforeButtonTitle')} "
                    f"buttonHidden={quick_objective_button_state.get('buttonHidden')} "
                    f"buttonTitle={quick_objective_button_state.get('buttonTitle')} "
                    f"fieldEnabled={(quick_objective_button_state.get('fieldEncounter') or {}).get('enabled')} "
                    f"autoSource={(quick_objective_button_state.get('fieldEncounterModeAutoSave') or {}).get('source')} "
                    f"payloadMap={(quick_objective_button_state.get('fieldEncounterModeAutoSave') or {}).get('payloadMap')} "
                    f"modeSource={(quick_objective_button_state.get('fieldEncounterMode') or {}).get('source')} "
                    f"savedPayloadMap={quick_objective_button_state.get('savedPayloadMap')} "
                    f"quickObjectiveSound={((quick_objective_button_state.get('quickAction') or {}).get('sound') or {}).get('source')}:"
                    f"{((quick_objective_button_state.get('quickAction') or {}).get('sound') or {}).get('inputSource')}:"
                    f"{((quick_objective_button_state.get('quickAction') or {}).get('sound') or {}).get('action')} "
                    f"quickObjectiveSoundSrc={((quick_objective_button_state.get('quickAction') or {}).get('sound') or {}).get('soundSrc')} "
                    f"quickObjectiveSoundPlayed={((quick_objective_button_state.get('quickAction') or {}).get('sound') or {}).get('soundPlayed')} "
                    f"menuConfirmSoundCount={quick_objective_button_state.get('menuConfirmSoundCount')} "
                    f"hud={'|'.join(str(line) for line in quick_objective_button_state.get('playHudLines') or [])} "
                    "quickObjectiveButton=True"
                ),
                "quickObjectiveToolbar": (
                    "map2_02d quickObjectiveToolbar "
                    f"source={(quick_objective_toolbar_state.get('quickAction') or {}).get('source')} "
                    f"action={(quick_objective_toolbar_state.get('quickAction') or {}).get('action')} "
                    f"handled={(quick_objective_toolbar_state.get('quickAction') or {}).get('handled')} "
                    f"beforeObjectiveButtonHidden={quick_objective_toolbar_state.get('beforeObjectiveButtonHidden')} "
                    f"beforeObjectiveButtonDisabled={quick_objective_toolbar_state.get('beforeObjectiveButtonDisabled')} "
                    f"beforeObjectiveButtonText={quick_objective_toolbar_state.get('beforeObjectiveButtonText')} "
                    f"beforeObjectiveButtonTitle={quick_objective_toolbar_state.get('beforeObjectiveButtonTitle')} "
                    f"objectiveButtonHidden={quick_objective_toolbar_state.get('objectiveButtonHidden')} "
                    f"objectiveButtonTitle={quick_objective_toolbar_state.get('objectiveButtonTitle')} "
                    f"routeGoal={quick_objective_toolbar_state.get('selectedRouteGoal')} "
                    f"routePathValue={quick_objective_toolbar_state.get('routePathValue')} "
                    f"transitionTarget={quick_objective_toolbar_state.get('transitionTargetParam')} "
                    f"routeNextText={quick_objective_toolbar_state.get('routeNextText')} "
                    f"routeNextTitle={quick_objective_toolbar_state.get('routeNextTitle')} "
                    f"quickObjectiveSound={((quick_objective_toolbar_state.get('quickAction') or {}).get('sound') or {}).get('source')}:"
                    f"{((quick_objective_toolbar_state.get('quickAction') or {}).get('sound') or {}).get('inputSource')}:"
                    f"{((quick_objective_toolbar_state.get('quickAction') or {}).get('sound') or {}).get('action')} "
                    f"quickObjectiveSoundSrc={((quick_objective_toolbar_state.get('quickAction') or {}).get('sound') or {}).get('soundSrc')} "
                    f"quickObjectiveSoundPlayed={((quick_objective_toolbar_state.get('quickAction') or {}).get('sound') or {}).get('soundPlayed')} "
                    f"menuConfirmSoundCount={quick_objective_toolbar_state.get('menuConfirmSoundCount')} "
                    f"hud={'|'.join(str(line) for line in quick_objective_toolbar_state.get('playHudLines') or [])} "
                    "quickObjectiveToolbar=True"
                ),
                "routeNextContinuationButton": (
                    "map2_02d routeNextContinuationButton "
                    f"activated={route_next_continuation_button_state.get('activated')} "
                    f"beforeRouteNextText={route_next_continuation_button_state.get('beforeRouteNextText')} "
                    f"beforeContinuationTarget={route_next_continuation_button_state.get('beforeRouteContinuationTarget')} "
                    f"routeGoal={route_next_continuation_button_state.get('selectedRouteGoal')} "
                    f"routePathValue={route_next_continuation_button_state.get('routePathValue')} "
                    f"transitionTarget={route_next_continuation_button_state.get('transitionTargetParam')} "
                    f"routeNextText={route_next_continuation_button_state.get('routeNextText')} "
                    f"routeNextTitle={route_next_continuation_button_state.get('routeNextTitle')} "
                    f"routeControlAction={(route_next_continuation_button_state.get('routeControlAction') or {}).get('action')} "
                    f"routeControlSound={((route_next_continuation_button_state.get('routeControlAction') or {}).get('sound') or {}).get('source')}:"
                    f"{((route_next_continuation_button_state.get('routeControlAction') or {}).get('sound') or {}).get('inputSource')}:"
                    f"{((route_next_continuation_button_state.get('routeControlAction') or {}).get('sound') or {}).get('action')} "
                    f"routeControlSoundSrc={((route_next_continuation_button_state.get('routeControlAction') or {}).get('sound') or {}).get('soundSrc')} "
                    f"routeControlSoundPlayed={((route_next_continuation_button_state.get('routeControlAction') or {}).get('sound') or {}).get('soundPlayed')} "
                    f"menuConfirmSoundCount={route_next_continuation_button_state.get('menuConfirmSoundCount')} "
                    f"hud={'|'.join(str(line) for line in route_next_continuation_button_state.get('playHudLines') or [])} "
                    "routeNextContinuation=True"
                ),
                "routeContinuationMenu": (
                    "map2_02d continuationMenu "
                    f"item={(continuation_state.get('continuationItem') or {}).get('name')} "
                    f"target={(continuation_state.get('continuationItem') or {}).get('routeTarget')} "
                    f"nextTarget={(continuation_state.get('continuationItem') or {}).get('routeNextTarget')} "
                    f"used={continuation_state.get('used')} "
                    f"routeGoal={continuation_state.get('selectedRouteGoal')} "
                    f"routePathValue={continuation_state.get('routePathValue')} "
                    f"transitionTarget={continuation_state.get('transitionTargetParam')} "
                    f"routeNextText={continuation_state.get('routeNextText')} "
                    f"routeNextTitle={continuation_state.get('routeNextTitle')} "
                    f"hud={'|'.join(str(line) for line in continuation_state.get('playHudLines') or [])} "
                    "runtimeMenuContinuation=True"
                ),
                "routeContinuationEntry": (
                    "map2_09g continuationEntry "
                    f"entered={continuation_entry_state.get('entered')} "
                    f"beforeMap={continuation_entry_state.get('beforeMap')} "
                    f"beforeRouteNextText={continuation_entry_state.get('beforeRouteNextText')} "
                    f"map={continuation_entry_state.get('map')} "
                    f"routeGoal={continuation_entry_state.get('selectedRouteGoal')} "
                    f"routePathValue={continuation_entry_state.get('routePathValue')} "
                    f"routeNextText={continuation_entry_state.get('routeNextText')} "
                    f"nextItem={(continuation_entry_state.get('nextContinuationItem') or {}).get('name')} "
                    f"nextTarget={(continuation_entry_state.get('nextContinuationItem') or {}).get('routeTarget')} "
                    f"autoSaved={(continuation_entry_state.get('routeAssistAutoSave') or {}).get('saved')} "
                    f"autoSource={(continuation_entry_state.get('routeAssistAutoSave') or {}).get('source')} "
                    f"payloadMap={(continuation_entry_state.get('routeAssistAutoSave') or {}).get('payloadMap')} "
                    f"inPlace={(continuation_entry_state.get('routeAssistInPlaceTransition') or {}).get('handled')} "
                    f"inPlaceSource={(continuation_entry_state.get('routeAssistInPlaceTransition') or {}).get('source')} "
                    f"inPlaceImplemented={(continuation_entry_state.get('routeAssistInPlaceTransition') or {}).get('browserRouteAssistInPlaceTransitionImplemented')} "
                    f"savedPayloadMap={continuation_entry_state.get('savedPayloadMap')} "
                    f"progressId={route_assist_progress_event(continuation_entry_state).get('id')} "
                    f"progressMap={route_assist_progress_event(continuation_entry_state).get('map')} "
                    f"candidateKind={route_assist_progress_detail(continuation_entry_state).get('candidateKind')} "
                    f"routeCandidateCount={route_candidate_count(continuation_entry_state)} "
                    f"savedRouteCandidateCount={saved_route_candidate_count(continuation_entry_state)} "
                    f"routeProgressFeedback={((continuation_entry_state.get('routeProgressFeedbackLast') or {}).get('source') or '')}:"
                    f"{((continuation_entry_state.get('routeProgressFeedbackLast') or {}).get('text') or '')} "
                    f"routeProgressFeedbackRender={bool(continuation_entry_state.get('routeProgressFeedbackRender') or [])} "
                    f"{route_progress_sound_summary(continuation_entry_state)} "
                    "pathProgress=후보 진행 1/2 "
                    f"hud={'|'.join(str(line) for line in continuation_entry_state.get('playHudLines') or [])} "
                    "runtimeMenuContinuationEntry=True"
                ),
                "routeContinuationChainMenu": (
                    "map2_09g continuationMenu "
                    f"item={(continuation_chain_state.get('continuationItem') or {}).get('name')} "
                    f"target={(continuation_chain_state.get('continuationItem') or {}).get('routeTarget')} "
                    f"nextTarget={(continuation_chain_state.get('continuationItem') or {}).get('routeNextTarget')} "
                    f"used={continuation_chain_state.get('used')} "
                    f"routeGoal={continuation_chain_state.get('selectedRouteGoal')} "
                    f"routePathValue={continuation_chain_state.get('routePathValue')} "
                    f"transitionTarget={continuation_chain_state.get('transitionTargetParam')} "
                    f"routeNextText={continuation_chain_state.get('routeNextText')} "
                    f"routeNextTitle={continuation_chain_state.get('routeNextTitle')} "
                    f"hud={'|'.join(str(line) for line in continuation_chain_state.get('playHudLines') or [])} "
                    "runtimeMenuContinuationChain=True"
                ),
                "routeContinuationChainEntry": (
                    "map2_10g continuationEntry "
                    f"entered={continuation_chain_entry_state.get('entered')} "
                    f"beforeMap={continuation_chain_entry_state.get('beforeMap')} "
                    f"beforeRouteNextText={continuation_chain_entry_state.get('beforeRouteNextText')} "
                    f"map={continuation_chain_entry_state.get('map')} "
                    f"routeGoal={continuation_chain_entry_state.get('selectedRouteGoal')} "
                    f"routePathValue={continuation_chain_entry_state.get('routePathValue')} "
                    f"routeNextText={continuation_chain_entry_state.get('routeNextText')} "
                    f"nextItem={(continuation_chain_entry_state.get('nextContinuationItem') or {}).get('name')} "
                    f"nextTarget={(continuation_chain_entry_state.get('nextContinuationItem') or {}).get('routeTarget')} "
                    f"autoSaved={(continuation_chain_entry_state.get('routeAssistAutoSave') or {}).get('saved')} "
                    f"autoSource={(continuation_chain_entry_state.get('routeAssistAutoSave') or {}).get('source')} "
                    f"payloadMap={(continuation_chain_entry_state.get('routeAssistAutoSave') or {}).get('payloadMap')} "
                    f"savedPayloadMap={continuation_chain_entry_state.get('savedPayloadMap')} "
                    f"progressId={route_assist_progress_event(continuation_chain_entry_state).get('id')} "
                    f"progressMap={route_assist_progress_event(continuation_chain_entry_state).get('map')} "
                    f"candidateKind={route_assist_progress_detail(continuation_chain_entry_state).get('candidateKind')} "
                    f"routeCandidateCount={route_candidate_count(continuation_chain_entry_state)} "
                    f"savedRouteCandidateCount={saved_route_candidate_count(continuation_chain_entry_state)} "
                    f"routeProgressFeedback={((continuation_chain_entry_state.get('routeProgressFeedbackLast') or {}).get('source') or '')}:"
                    f"{((continuation_chain_entry_state.get('routeProgressFeedbackLast') or {}).get('text') or '')} "
                    f"routeProgressFeedbackRender={bool(continuation_chain_entry_state.get('routeProgressFeedbackRender') or [])} "
                    f"{route_progress_sound_summary(continuation_chain_entry_state)} "
                    "pathProgress=후보 진행 2/3 "
                    f"hud={'|'.join(str(line) for line in continuation_chain_entry_state.get('playHudLines') or [])} "
                    "runtimeMenuContinuationChainEntry=True"
                ),
                "routeContinuationTitleRestore": (
                    "map2_10g titleRestore "
                    f"titleContinue={continuation_title_restore.get('titleContinue')} "
                    f"label={continuation_title_label} "
                    f"map={continuation_title_restore.get('map')}@{(continuation_title_restore.get('tile') or {}).get('x')},{(continuation_title_restore.get('tile') or {}).get('y')} "
                    f"quickLoadText={continuation_title_restore.get('quickLoadText')} "
                    f"routeGoal={continuation_title_restore.get('routePathValue') or continuation_title_restore.get('selectedRouteGoal')} "
                    f"nextItem={(continuation_title_restore.get('continuationItem') or {}).get('name')} "
                    f"nextTarget={(continuation_title_restore.get('continuationItem') or {}).get('routeTarget')} "
                    f"savedPayloadMap={continuation_title_restore.get('savedPayloadMap')} "
                    f"routeCandidateCount={route_candidate_count(continuation_title_restore)} "
                    f"savedRouteCandidateCount={saved_route_candidate_count(continuation_title_restore)} "
                    "pathProgress=후보 진행 2/3 "
                    f"hud={'|'.join(str(line) for line in continuation_title_restore.get('playHudLines') or [])} "
                    "runtimeMenuContinuationTitleRestore=True"
                ),
                "routeContinuationRestoredMenu": (
                    "map2_10g restoredContinuationMenu "
                    f"item={(continuation_restored_state.get('continuationItem') or {}).get('name')} "
                    f"target={(continuation_restored_state.get('continuationItem') or {}).get('routeTarget')} "
                    f"nextTarget={(continuation_restored_state.get('continuationItem') or {}).get('routeNextTarget')} "
                    f"used={continuation_restored_state.get('used')} "
                    f"routeGoal={continuation_restored_state.get('selectedRouteGoal')} "
                    f"routePathValue={continuation_restored_state.get('routePathValue')} "
                    f"transitionTarget={continuation_restored_state.get('transitionTargetParam')} "
                    f"routeNextText={continuation_restored_state.get('routeNextText')} "
                    f"routeNextTitle={continuation_restored_state.get('routeNextTitle')} "
                    f"hud={'|'.join(str(line) for line in continuation_restored_state.get('playHudLines') or [])} "
                    "runtimeMenuContinuationRestored=True"
                ),
                "routeContinuationRestoredEntry": (
                    "map2_12h restoredContinuationEntry "
                    f"entered={continuation_restored_entry_state.get('entered')} "
                    f"beforeMap={continuation_restored_entry_state.get('beforeMap')} "
                    f"beforeRouteNextText={continuation_restored_entry_state.get('beforeRouteNextText')} "
                    f"map={continuation_restored_entry_state.get('map')} "
                    f"routeGoal={continuation_restored_entry_state.get('selectedRouteGoal')} "
                    f"routePathValue={continuation_restored_entry_state.get('routePathValue')} "
                    f"routeNextText={continuation_restored_entry_state.get('routeNextText')} "
                    f"nextItem={(continuation_restored_entry_state.get('nextContinuationItem') or {}).get('name')} "
                    f"nextTarget={(continuation_restored_entry_state.get('nextContinuationItem') or {}).get('routeTarget')} "
                    f"autoSaved={(continuation_restored_entry_state.get('routeAssistAutoSave') or {}).get('saved')} "
                    f"autoSource={(continuation_restored_entry_state.get('routeAssistAutoSave') or {}).get('source')} "
                    f"payloadMap={(continuation_restored_entry_state.get('routeAssistAutoSave') or {}).get('payloadMap')} "
                    f"savedPayloadMap={continuation_restored_entry_state.get('savedPayloadMap')} "
                    f"progressId={route_assist_progress_event(continuation_restored_entry_state).get('id')} "
                    f"progressMap={route_assist_progress_event(continuation_restored_entry_state).get('map')} "
                    f"candidateKind={route_assist_progress_detail(continuation_restored_entry_state).get('candidateKind')} "
                    f"routeCandidateCount={route_candidate_count(continuation_restored_entry_state)} "
                    f"savedRouteCandidateCount={saved_route_candidate_count(continuation_restored_entry_state)} "
                    f"routeProgressFeedback={((continuation_restored_entry_state.get('routeProgressFeedbackLast') or {}).get('source') or '')}:"
                    f"{((continuation_restored_entry_state.get('routeProgressFeedbackLast') or {}).get('text') or '')} "
                    f"routeProgressFeedbackRender={bool(continuation_restored_entry_state.get('routeProgressFeedbackRender') or [])} "
                    f"{route_progress_sound_summary(continuation_restored_entry_state)} "
                    "pathProgress=후보 진행 3/4 "
                    f"hud={'|'.join(str(line) for line in continuation_restored_entry_state.get('playHudLines') or [])} "
                    "runtimeMenuContinuationRestoredEntry=True"
                ),
                "routeContinuationRestoredTitleRestore": (
                    "map2_12h restoredTitleRestore "
                    f"titleContinue={restored_title_restore.get('titleContinue')} "
                    f"label={restored_title_label} "
                    f"map={restored_title_restore.get('map')}@{(restored_title_restore.get('tile') or {}).get('x')},{(restored_title_restore.get('tile') or {}).get('y')} "
                    f"quickLoadText={restored_title_restore.get('quickLoadText')} "
                    f"routeGoal={restored_title_restore.get('routePathValue') or restored_title_restore.get('selectedRouteGoal')} "
                    f"nextItem={(restored_title_restore.get('continuationItem') or {}).get('name')} "
                    f"nextTarget={(restored_title_restore.get('continuationItem') or {}).get('routeTarget')} "
                    f"savedPayloadMap={restored_title_restore.get('savedPayloadMap')} "
                    f"routeCandidateCount={route_candidate_count(restored_title_restore)} "
                    f"savedRouteCandidateCount={saved_route_candidate_count(restored_title_restore)} "
                    "pathProgress=후보 진행 3/4 "
                    f"hud={'|'.join(str(line) for line in restored_title_restore.get('playHudLines') or [])} "
                    "runtimeMenuContinuationRestoredTitleRestore=True"
                ),
                "routeContinuationFinalMenu": (
                    "map2_12h finalContinuationMenu "
                    f"item={(continuation_final_state.get('continuationItem') or {}).get('name')} "
                    f"target={(continuation_final_state.get('continuationItem') or {}).get('routeTarget')} "
                    f"nextTarget={(continuation_final_state.get('continuationItem') or {}).get('routeNextTarget')} "
                    f"used={continuation_final_state.get('used')} "
                    f"routeGoal={continuation_final_state.get('selectedRouteGoal')} "
                    f"routePathValue={continuation_final_state.get('routePathValue')} "
                    f"transitionTarget={continuation_final_state.get('transitionTargetParam')} "
                    f"routeNextText={continuation_final_state.get('routeNextText')} "
                    f"routeNextTitle={continuation_final_state.get('routeNextTitle')} "
                    f"hud={'|'.join(str(line) for line in continuation_final_state.get('playHudLines') or [])} "
                    "runtimeMenuContinuationFinal=True"
                ),
                "routeContinuationFinalEntry": (
                    "map2_14j finalContinuationEntry "
                    f"entered={continuation_final_entry_state.get('entered')} "
                    f"beforeMap={continuation_final_entry_state.get('beforeMap')} "
                    f"beforeRouteNextText={continuation_final_entry_state.get('beforeRouteNextText')} "
                    f"map={continuation_final_entry_state.get('map')} "
                    f"routeGoal={continuation_final_entry_state.get('selectedRouteGoal')} "
                    f"routePathValue={continuation_final_entry_state.get('routePathValue')} "
                    f"routeNextText={continuation_final_entry_state.get('routeNextText')} "
                    f"nextItem={(continuation_final_entry_state.get('nextContinuationItem') or {}).get('name')} "
                    f"nextTarget={(continuation_final_entry_state.get('nextContinuationItem') or {}).get('routeTarget')} "
                    f"autoSaved={(continuation_final_entry_state.get('routeAssistAutoSave') or {}).get('saved')} "
                    f"autoSource={(continuation_final_entry_state.get('routeAssistAutoSave') or {}).get('source')} "
                    f"payloadMap={(continuation_final_entry_state.get('routeAssistAutoSave') or {}).get('payloadMap')} "
                    f"savedPayloadMap={continuation_final_entry_state.get('savedPayloadMap')} "
                    f"progressId={route_assist_progress_event(continuation_final_entry_state).get('id')} "
                    f"progressMap={route_assist_progress_event(continuation_final_entry_state).get('map')} "
                    f"candidateKind={route_assist_progress_detail(continuation_final_entry_state).get('candidateKind')} "
                    f"routeCandidateCount={route_candidate_count(continuation_final_entry_state)} "
                    f"savedRouteCandidateCount={saved_route_candidate_count(continuation_final_entry_state)} "
                    f"routeProgressFeedback={((continuation_final_entry_state.get('routeProgressFeedbackLast') or {}).get('source') or '')}:"
                    f"{((continuation_final_entry_state.get('routeProgressFeedbackLast') or {}).get('text') or '')} "
                    f"routeProgressFeedbackRender={bool(continuation_final_entry_state.get('routeProgressFeedbackRender') or [])} "
                    f"{route_progress_sound_summary(continuation_final_entry_state)} "
                    "pathProgress=후보 진행 4/5 "
                    f"hud={'|'.join(str(line) for line in continuation_final_entry_state.get('playHudLines') or [])} "
                    "runtimeMenuContinuationFinalEntry=True"
                ),
                "routeContinuationFinalTitleRestore": (
                    "map2_14j finalTitleRestore "
                    f"titleContinue={final_title_restore.get('titleContinue')} "
                    f"label={final_title_label} "
                    f"map={final_title_restore.get('map')}@{(final_title_restore.get('tile') or {}).get('x')},{(final_title_restore.get('tile') or {}).get('y')} "
                    f"quickLoadText={final_title_restore.get('quickLoadText')} "
                    f"routeGoal={final_title_restore.get('routePathValue') or final_title_restore.get('selectedRouteGoal')} "
                    f"nextItem={(final_title_restore.get('continuationItem') or {}).get('name')} "
                    f"nextTarget={(final_title_restore.get('continuationItem') or {}).get('routeTarget')} "
                    f"savedPayloadMap={final_title_restore.get('savedPayloadMap')} "
                    f"routeCandidateCount={route_candidate_count(final_title_restore)} "
                    f"savedRouteCandidateCount={saved_route_candidate_count(final_title_restore)} "
                    "pathProgress=후보 진행 4/5 "
                    f"hud={'|'.join(str(line) for line in final_title_restore.get('playHudLines') or [])} "
                    "runtimeMenuContinuationFinalTitleRestore=True"
                ),
                "routeContinuationFinalFieldEncounter": (
                    "map2_14j finalFieldEncounter "
                    f"started={continuation_final_field_encounter.get('started')} "
                    f"input={continuation_final_field_encounter.get('inputCode')} "
                    f"from={field_encounter_before_foot.get('x')},{field_encounter_before_foot.get('y')} "
                    f"to={field_encounter_after_foot.get('x')},{field_encounter_after_foot.get('y')} "
                    f"battleId={field_encounter_battle.get('id')} "
                    f"battleBackground={field_encounter_battle.get('battleBackground')} "
                    f"routeGoal={(continuation_final_field_encounter.get('routeState') or {}).get('selectedRouteGoal')} "
                    f"routeCandidateCount={progress_count(continuation_final_field_encounter, 'route-candidate')} "
                    f"battleStartCount={progress_count(continuation_final_field_encounter, 'battle-start')} "
                    f"fieldEncounterCount={progress_count(continuation_final_field_encounter, 'field-encounter')} "
                    f"fieldEncounterVictoryCount={progress_count(continuation_final_field_encounter, 'field-encounter-victory')} "
                    f"battleVictoryCount={progress_count(continuation_final_field_encounter, 'battle-victory') or 0} "
                    f"completionCompleted={(continuation_final_field_encounter.get('completion') or {}).get('completed')} "
                    f"completionSource={route_completion(continuation_final_field_encounter).get('source')} "
                    f"completionRouteCandidateCount={route_completion(continuation_final_field_encounter).get('routeCandidateCount')} "
                    f"completionFieldEncounterVictoryCount={route_completion(continuation_final_field_encounter).get('fieldEncounterVictoryCount')} "
                    f"autoSaved={field_encounter_auto_save.get('saved')} "
                    f"autoSource={field_encounter_auto_save.get('source')} "
                    f"saveMap={field_encounter_auto_save.get('payloadMap')} "
                    f"saveTile={field_encounter_auto_tile.get('x')},{field_encounter_auto_tile.get('y')} "
                    f"savedPayloadMap={(continuation_final_field_encounter.get('savedPayload') or {}).get('map')} "
                    f"hud={'|'.join(str(line) for line in continuation_final_field_encounter.get('playHudLines') or [])} "
                    "runtimeMenuContinuationFinalFieldEncounter=True "
                    "originalEncounterRuntimeImplemented=False"
                ),
                "routeContinuationFinalFieldEncounterTitleRestore": (
                    "map2_14j finalFieldEncounterTitleRestore "
                    f"titleContinue={field_encounter_title_restore.get('titleContinue')} "
                    f"label={field_encounter_title_label} "
                    f"map={field_encounter_title_restore.get('map')}@{(field_encounter_title_restore.get('tile') or {}).get('x')},{(field_encounter_title_restore.get('tile') or {}).get('y')} "
                    f"quickLoadText={field_encounter_title_restore.get('quickLoadText')} "
                    f"routeGoal={field_encounter_title_restore.get('routePathValue') or field_encounter_title_restore.get('selectedRouteGoal')} "
                    f"routeCandidateCount={route_candidate_count(field_encounter_title_restore)} "
                    f"savedRouteCandidateCount={saved_route_candidate_count(field_encounter_title_restore)} "
                    f"battleStartCount={progress_count(field_encounter_title_restore, 'battle-start')} "
                    f"fieldEncounterCount={progress_count(field_encounter_title_restore, 'field-encounter')} "
                    f"fieldEncounterVictoryCount={progress_count(field_encounter_title_restore, 'field-encounter-victory')} "
                    f"savedFieldEncounterVictoryCount={saved_progress_count(field_encounter_title_restore, 'field-encounter-victory')} "
                    f"battleVictoryCount={progress_count(field_encounter_title_restore, 'battle-victory') or 0} "
                    f"completionCompleted={(field_encounter_title_restore.get('completion') or {}).get('completed')} "
                    f"completionSource={route_completion(field_encounter_title_restore).get('source')} "
                    f"completionRouteCandidateCount={route_completion(field_encounter_title_restore).get('routeCandidateCount')} "
                    f"completionFieldEncounterVictoryCount={route_completion(field_encounter_title_restore).get('fieldEncounterVictoryCount')} "
                    "pathProgress=후보 진행 4/5 "
                    f"hud={'|'.join(str(line) for line in field_encounter_title_restore.get('playHudLines') or [])} "
                    "runtimeMenuContinuationFinalFieldEncounterTitleRestore=True"
                ),
                "routeCompletionNotice": (
                    "map2_14j routeCompletionNotice "
                    f"activeId={route_completion_notice.get('activeId')} "
                    f"beforeRouteNextText={(route_completion_notice.get('before') or {}).get('routeNextText')} "
                    f"routeCompleted={(route_completion_notice.get('before') or {}).get('routeCompleted')} "
                    f"noticeBlock={(route_completion_notice.get('notice') or {}).get('blockId')} "
                    f"completionCompleted={(route_completion_notice.get('completion') or {}).get('completed')} "
                    f"completionSource={route_completion(route_completion_notice).get('source')} "
                    f"completionRouteCandidateCount={route_completion(route_completion_notice).get('routeCandidateCount')} "
                    f"completionFieldEncounterVictoryCount={route_completion(route_completion_notice).get('fieldEncounterVictoryCount')} "
                    f"completionRouteCompleteCount={route_completion(route_completion_notice).get('routeCompleteCount')} "
                    f"routeCompleteRecorded={route_completion(route_completion_notice).get('routeCompleteRecorded')} "
                    f"routeCompleteCount={(route_completion_notice.get('notice') or {}).get('routeCompleteCount')} "
                    f"routeCompleteProgress={(route_completion_notice.get('notice') or {}).get('progressEvent', {}).get('kind')} "
                    f"routeCompleteAutoSource={(route_completion_notice.get('notice') or {}).get('autoSave', {}).get('source')} "
                    f"routeCompleteSavedPayloadMap={(route_completion_notice.get('notice') or {}).get('autoSave', {}).get('payloadMap')} "
                    f"{story_flag_report((route_completion_notice.get('notice') or {}).get('autoSave', {}).get('storyFlags'), 'routeComplete')} "
                    f"playableGate={(route_completion_notice.get('notice') or {}).get('playableGate', {}).get('source')} "
                    f"playableGateStatus={(route_completion_notice.get('notice') or {}).get('playableGate', {}).get('status')} "
                    f"playableGateOpen={(route_completion_notice.get('notice') or {}).get('playableGate', {}).get('opened')} "
                    f"playableGateLabel={(route_completion_notice.get('notice') or {}).get('playableGate', {}).get('label')} "
                    f"originalFullGameCompletionImplemented={(route_completion_notice.get('notice') or {}).get('playableGate', {}).get('originalFullGameCompletionImplemented')} "
                    f"menuCompletionResult={route_completion_notice.get('menuCompletionResult')} "
                    f"menuActiveId={route_completion_notice.get('menuActiveId')} "
                    "menuCommand=후보 완료 map2_14j "
                    "progressReviewRouteComplete=True "
                    f"objectiveAction={(route_completion_notice.get('objectiveAction') or {}).get('action')} "
                    f"objectiveActiveId={route_completion_notice.get('objectiveActiveId')} "
                    f"routeGateFeedback={((route_completion_notice.get('routeGateFeedbackLast') or {}).get('source') or '')}:"
                    f"{((route_completion_notice.get('routeGateFeedbackLast') or {}).get('text') or '')} "
                    f"routeGateFeedbackRender={bool(route_completion_notice.get('routeGateFeedbackRender') or [])} "
                    f"routeGateSound={((route_completion_notice.get('routeGateFeedbackLast') or {}).get('routeGateSound') or '')} "
                    f"routeGateSoundSrc={((route_completion_notice.get('routeGateFeedbackLast') or {}).get('routeGateSoundSrc') or '')} "
                    f"routeGateSoundPlayed={((route_completion_notice.get('routeGateFeedbackLast') or {}).get('routeGateSoundPlayed'))} "
                    f"hud={'|'.join(str(line) for line in route_completion_notice.get('playHudLines') or [])} "
                    f"lines={'|'.join(str(line) for line in route_completion_notice.get('activeLines') or [])} "
                    "runtimeRouteCompletionNotice=True "
                    "originalRoutePromotionImplemented=False"
                ),
                "completionGateTitleRestore": (
                    "map2_14j completionGateTitleRestore "
                    f"titleContinue={completion_gate_title_restore.get('titleContinue')} "
                    f"label={completion_gate_title_label} "
                    f"map={completion_gate_title_restore.get('map')}@{(completion_gate_title_restore.get('tile') or {}).get('x')},{(completion_gate_title_restore.get('tile') or {}).get('y')} "
                    f"quickLoadText={completion_gate_title_restore.get('quickLoadText')} "
                    f"routeGoal={completion_gate_title_restore.get('routePathValue') or completion_gate_title_restore.get('selectedRouteGoal')} "
                    f"routeCandidateCount={route_candidate_count(completion_gate_title_restore)} "
                    f"savedRouteCandidateCount={saved_route_candidate_count(completion_gate_title_restore)} "
                    f"routeCompleteCount={progress_count(completion_gate_title_restore, 'route-complete')} "
                    f"savedRouteCompleteCount={saved_progress_count(completion_gate_title_restore, 'route-complete')} "
                    f"{story_flag_report(completion_gate_title_restore.get('savedStoryFlags'), 'savedRouteComplete')} "
                    f"completionRouteCompleteCount={route_completion(completion_gate_title_restore).get('routeCompleteCount')} "
                    f"playableGate={(completion_gate_title_restore.get('completion') or {}).get('playableGate', {}).get('source')} "
                    f"playableGateStatus={(completion_gate_title_restore.get('completion') or {}).get('playableGate', {}).get('status')} "
                    f"playableGateOpen={(completion_gate_title_restore.get('completion') or {}).get('playableGateOpen')} "
                    f"playableGateLabel={(completion_gate_title_restore.get('completion') or {}).get('playableGate', {}).get('label')} "
                    f"completionItem={(completion_gate_title_restore.get('completionItem') or {}).get('name')} "
                    f"routeNextText={completion_gate_title_restore.get('routeNextText')} "
                    f"hud={'|'.join(str(line) for line in completion_gate_title_restore.get('playHudLines') or [])} "
                    "runtimeRouteCompletionGateTitleRestore=True "
                    "originalFullGameCompletionImplemented=False"
                ),
                "restoredCompletionMenuNotice": (
                    "map2_14j restoredCompletionMenuNotice "
                    f"result={restored_completion_menu_notice.get('result')} "
                    f"activeId={restored_completion_menu_notice.get('activeId')} "
                    f"routeCompleteCount={(restored_completion_menu_notice.get('notice') or {}).get('routeCompleteCount')} "
                    f"routeCompleteProgress={(restored_completion_menu_notice.get('notice') or {}).get('progressEvent', {}).get('kind')} "
                    f"routeCompleteAutoSource={(restored_completion_menu_notice.get('notice') or {}).get('autoSave', {}).get('source')} "
                    f"duplicateProgress={(restored_completion_menu_notice.get('notice') or {}).get('autoSave', {}).get('duplicateProgress')} "
                    f"savedRouteCompleteCount={((restored_completion_menu_notice.get('notice') or {}).get('autoSave', {}).get('progress') or {}).get('counts', {}).get('route-complete')} "
                    f"playableGate={(restored_completion_menu_notice.get('completion') or {}).get('playableGate', {}).get('source')} "
                    f"playableGateStatus={(restored_completion_menu_notice.get('completion') or {}).get('playableGate', {}).get('status')} "
                    f"playableGateOpen={(restored_completion_menu_notice.get('completion') or {}).get('playableGateOpen')} "
                    "menuCommand=후보 완료 map2_14j "
                    "progressReviewRouteComplete=True "
                    f"lines={'|'.join(str(line) for line in restored_completion_menu_notice.get('activeLines') or [])} "
                    "runtimeRestoredRouteCompletionMenuNotice=True"
                ),
                "titleRouteCompletionGate": (
                    "map2_14j titleRouteCompletionGate "
                    f"titleItem={(title_route_completion_gate.get('click') or {}).get('titleItem', {}).get('label')} "
                    f"titleKeys={','.join(str(key) for key in (title_route_completion_gate.get('click') or {}).get('titleMenuKeys') or [])} "
                    f"loaded={(title_route_completion_gate.get('titleGate') or {}).get('loaded')} "
                    f"opened={(title_route_completion_gate.get('titleGate') or {}).get('opened')} "
                    f"activeId={title_route_completion_gate.get('activeId')} "
                    f"map={title_route_completion_gate.get('map')} "
                    f"routeCompleteCount={((title_route_completion_gate.get('completion') or {}).get('route') or {}).get('routeCompleteCount')} "
                    f"payloadRouteCompleteCount={(title_route_completion_gate.get('payloadCounts') or {}).get('route-complete')} "
                    f"{story_flag_report(title_route_completion_gate.get('payloadStoryFlags'), 'payloadRouteComplete')} "
                    f"titlePayloadCount={(title_route_completion_gate.get('titleRouteCompletionPayload') or {}).get('routeCompleteCount')} "
                    f"playableGate={(title_route_completion_gate.get('completion') or {}).get('playableGate', {}).get('source')} "
                    f"playableGateStatus={(title_route_completion_gate.get('completion') or {}).get('playableGate', {}).get('status')} "
                    f"playableGateOpen={(title_route_completion_gate.get('completion') or {}).get('playableGateOpen')} "
                    f"routeCompleteAutoSource={(title_route_completion_gate.get('notice') or {}).get('autoSave', {}).get('source')} "
                    f"duplicateProgress={(title_route_completion_gate.get('notice') or {}).get('autoSave', {}).get('duplicateProgress')} "
                    f"routeGateFeedback={((title_route_completion_gate.get('routeGateFeedbackLast') or {}).get('source') or '')}:"
                    f"{((title_route_completion_gate.get('routeGateFeedbackLast') or {}).get('text') or '')} "
                    f"routeGateFeedbackRender={bool(title_route_completion_gate.get('routeGateFeedbackRender') or [])} "
                    f"routeGateSound={((title_route_completion_gate.get('routeGateFeedbackLast') or {}).get('routeGateSound') or '')} "
                    f"routeGateSoundSrc={((title_route_completion_gate.get('routeGateFeedbackLast') or {}).get('routeGateSoundSrc') or '')} "
                    f"routeGateSoundPlayed={((title_route_completion_gate.get('routeGateFeedbackLast') or {}).get('routeGateSoundPlayed'))} "
                    f"lines={'|'.join(str(line) for line in title_route_completion_gate.get('activeLines') or [])} "
                    "runtimeTitleRouteCompletionGate=True"
                ),
                "titleRouteClearGate": (
                    "map2_14j titleRouteClearGate "
                    f"titleItem={(title_route_clear_gate.get('click') or {}).get('titleItem', {}).get('label')} "
                    f"titleKeys={','.join(str(key) for key in (title_route_clear_gate.get('click') or {}).get('titleMenuKeys') or [])} "
                    f"loaded={(title_route_clear_gate.get('titleGate') or {}).get('loaded')} "
                    f"opened={(title_route_clear_gate.get('titleGate') or {}).get('opened')} "
                    f"activeId={title_route_clear_gate.get('activeId')} "
                    f"map={title_route_clear_gate.get('map')} "
                    f"routeCompleteCount={((title_route_clear_gate.get('completion') or {}).get('route') or {}).get('routeCompleteCount')} "
                    f"routeClearCount={((title_route_clear_gate.get('completion') or {}).get('route') or {}).get('routeClearCount')} "
                    f"payloadRouteClearCount={(title_route_clear_gate.get('payloadCounts') or {}).get('route-clear')} "
                    f"{story_flag_report(title_route_clear_gate.get('payloadStoryFlags'), 'payloadRouteClear')} "
                    f"titlePayloadClearCount={(title_route_clear_gate.get('titleRouteClearPayload') or {}).get('routeClearCount')} "
                    f"titlePayloadLabel={(title_route_clear_gate.get('titleRouteClearPayload') or {}).get('label')} "
                    f"routeClearProgress={(title_route_clear_gate.get('summary') or {}).get('progressEvent', {}).get('kind')} "
                    f"routeClearAutoSource={(title_route_clear_gate.get('summary') or {}).get('autoSave', {}).get('source')} "
                    f"duplicateProgress={(title_route_clear_gate.get('summary') or {}).get('autoSave', {}).get('duplicateProgress')} "
                    f"playableGate={(title_route_clear_gate.get('completion') or {}).get('playableGate', {}).get('source')} "
                    f"playableGateStatus={(title_route_clear_gate.get('completion') or {}).get('playableGate', {}).get('status')} "
                    f"playableGateOpen={(title_route_clear_gate.get('completion') or {}).get('playableGateOpen')} "
                    "menuCommand=후보 클리어 map2_14j "
                    "progressReviewRouteClear=True "
                    f"routeGateFeedback={((title_route_clear_gate.get('routeGateFeedbackLast') or {}).get('source') or '')}:"
                    f"{((title_route_clear_gate.get('routeGateFeedbackLast') or {}).get('text') or '')} "
                    f"routeGateFeedbackRender={bool(title_route_clear_gate.get('routeGateFeedbackRender') or [])} "
                    f"routeGateSound={((title_route_clear_gate.get('routeGateFeedbackLast') or {}).get('routeGateSound') or '')} "
                    f"routeGateSoundSrc={((title_route_clear_gate.get('routeGateFeedbackLast') or {}).get('routeGateSoundSrc') or '')} "
                    f"routeGateSoundPlayed={((title_route_clear_gate.get('routeGateFeedbackLast') or {}).get('routeGateSoundPlayed'))} "
                    f"titleContinueLabel={title_route_clear_gate.get('titleContinueLabel')} "
                    f"progressReview={'|'.join(str(line) for line in (title_route_clear_gate.get('progressReviewBlock') or {}).get('lines') or [])} "
                    f"lines={'|'.join(str(line) for line in title_route_clear_gate.get('activeLines') or [])} "
                    "runtimeTitleRouteClearGate=True"
                ),
                "routeClearGateTitleRestore": (
                    "map2_14j routeClearGateTitleRestore "
                    f"titleContinue={route_clear_gate_title_restore.get('titleContinue')} "
                    f"label={route_clear_gate_title_label} "
                    f"titleRouteCompletionLabelHidden={'완료 map2_14j' not in route_clear_gate_title_label} "
                    f"titleRouteCompletionHidden={'routeCompletion' not in route_clear_gate_continue_keys} "
                    f"titleRouteClearVisible={'routeClear' in route_clear_gate_continue_keys} "
                    f"map={route_clear_gate_title_restore.get('map')}@{(route_clear_gate_title_restore.get('tile') or {}).get('x')},{(route_clear_gate_title_restore.get('tile') or {}).get('y')} "
                    f"quickLoadText={route_clear_gate_title_restore.get('quickLoadText')} "
                    f"routeGoal={route_clear_gate_title_restore.get('routePathValue') or route_clear_gate_title_restore.get('selectedRouteGoal')} "
                    f"routeCompleteCount={progress_count(route_clear_gate_title_restore, 'route-complete')} "
                    f"routeClearCount={progress_count(route_clear_gate_title_restore, 'route-clear')} "
                    f"savedRouteClearCount={saved_progress_count(route_clear_gate_title_restore, 'route-clear')} "
                    f"{story_flag_report(route_clear_gate_title_restore.get('savedStoryFlags'), 'savedRouteClear')} "
                    f"completionRouteClearCount={route_completion(route_clear_gate_title_restore).get('routeClearCount')} "
                    f"playableGate={(route_clear_gate_title_restore.get('completion') or {}).get('playableGate', {}).get('source')} "
                f"playableGateStatus={(route_clear_gate_title_restore.get('completion') or {}).get('playableGate', {}).get('status')} "
                f"playableGateOpen={(route_clear_gate_title_restore.get('completion') or {}).get('playableGateOpen')} "
                f"completionItem={(route_clear_gate_title_restore.get('completionItem') or {}).get('name') or '-'} "
                f"clearItem={(route_clear_gate_title_restore.get('clearItem') or {}).get('name')} "
                f"clearLabelCount={sum(1 for label in route_clear_gate_title_restore.get('labels') or [] if str(label) == '후보 클리어 map2_14j')} "
                f"routeNextText={route_clear_gate_title_restore.get('routeNextText')} "
                    f"routeNextTitle={route_clear_gate_title_restore.get('routeNextTitle')} "
                    f"hud={'|'.join(str(line) for line in route_clear_gate_title_restore.get('playHudLines') or [])} "
                    "runtimeRouteClearGateTitleRestore=True "
                    "originalFullGameCompletionImplemented=False"
                ),
                "routeClearControls": (
                    "map2_14j routeClearControls "
                    f"beforeRouteNextText={(route_clear_controls.get('before') or {}).get('routeNextText')} "
                    f"beforeRouteCleared={(route_clear_controls.get('before') or {}).get('routeCleared')} "
                    f"objectiveAction={(route_clear_controls.get('objectiveAction') or {}).get('action')} "
                    f"objectiveActiveId={route_clear_controls.get('objectiveActiveId')} "
                    f"objectiveAutoSource={(route_clear_controls.get('objectiveSummary') or {}).get('autoSave', {}).get('source')} "
                    f"objectiveDuplicateProgress={(route_clear_controls.get('objectiveSummary') or {}).get('autoSave', {}).get('duplicateProgress')} "
                    f"buttonResult={route_clear_controls.get('buttonResult')} "
                    f"buttonActiveId={route_clear_controls.get('buttonActiveId')} "
                    f"buttonAutoSource={(route_clear_controls.get('buttonSummary') or {}).get('autoSave', {}).get('source')} "
                    f"buttonDuplicateProgress={(route_clear_controls.get('buttonSummary') or {}).get('autoSave', {}).get('duplicateProgress')} "
                    f"routeClearCount={((route_clear_controls.get('completion') or {}).get('route') or {}).get('routeClearCount')} "
                    f"routeGateFeedback={((route_clear_controls.get('routeGateFeedbackLast') or {}).get('source') or '')}:"
                    f"{((route_clear_controls.get('routeGateFeedbackLast') or {}).get('text') or '')} "
                    f"routeGateFeedbackRender={bool(route_clear_controls.get('routeGateFeedbackRender') or [])} "
                    f"routeGateSound={((route_clear_controls.get('routeGateFeedbackLast') or {}).get('routeGateSound') or '')} "
                    f"routeGateSoundSrc={((route_clear_controls.get('routeGateFeedbackLast') or {}).get('routeGateSoundSrc') or '')} "
                    f"routeGateSoundPlayed={((route_clear_controls.get('routeGateFeedbackLast') or {}).get('routeGateSoundPlayed'))} "
                    f"hud={'|'.join(str(line) for line in route_clear_controls.get('playHudLines') or [])} "
                    f"progressReview={'|'.join(str(line) for line in (route_clear_controls.get('progressReviewBlock') or {}).get('lines') or [])} "
                    f"buttonLines={'|'.join(str(line) for line in route_clear_controls.get('buttonActiveLines') or [])} "
                    "runtimeRouteClearObjectiveAndButton=True"
                ),
                "restoredRouteClearMenuSummary": (
                    "map2_14j restoredRouteClearMenuSummary "
                    f"result={restored_route_clear_menu_summary.get('result')} "
                    f"activeId={restored_route_clear_menu_summary.get('activeId')} "
                    f"routeClearCount={(restored_route_clear_menu_summary.get('summary') or {}).get('routeClearCount')} "
                    f"routeClearProgress={(restored_route_clear_menu_summary.get('summary') or {}).get('progressEvent', {}).get('kind')} "
                    f"routeClearAutoSource={(restored_route_clear_menu_summary.get('summary') or {}).get('autoSave', {}).get('source')} "
                    f"duplicateProgress={(restored_route_clear_menu_summary.get('summary') or {}).get('autoSave', {}).get('duplicateProgress')} "
                    f"savedRouteClearCount={((restored_route_clear_menu_summary.get('summary') or {}).get('autoSave', {}).get('progress') or {}).get('counts', {}).get('route-clear')} "
                    f"playableGate={(restored_route_clear_menu_summary.get('completion') or {}).get('playableGate', {}).get('source')} "
                    f"playableGateStatus={(restored_route_clear_menu_summary.get('completion') or {}).get('playableGate', {}).get('status')} "
                    f"playableGateOpen={(restored_route_clear_menu_summary.get('completion') or {}).get('playableGateOpen')} "
                    "menuCommand=후보 클리어 map2_14j "
                    "progressReviewRouteClear=True "
                    f"progressReview={'|'.join(str(line) for line in (restored_route_clear_menu_summary.get('progressReviewBlock') or {}).get('lines') or [])} "
                    f"lines={'|'.join(str(line) for line in restored_route_clear_menu_summary.get('activeLines') or [])} "
                    "runtimeRestoredRouteClearMenuSummary=True"
                ),
                "defaultMapExit": (
                    "map1_01a->map2_02d "
                    f"map={default_map_exit_state.get('map')} "
                    f"payloadMap={default_map_exit_state.get('payloadMap')} "
                    f"routeCandidateCount={(default_map_exit_state.get('progress') or {}).get('counts', {}).get('route-candidate')} "
                    f"trialTransitionMode={((default_map_exit_state.get('progressRecord') or {}).get('detail') or {}).get('trialTransitionMode')} "
                    f"autoSaved={(default_map_exit_state.get('autoSave') or {}).get('saved')} "
                    f"autoSource={(default_map_exit_state.get('autoSave') or {}).get('source')} "
                    f"prototypeMapExit={((default_map_exit_state.get('prototypeMapExitTransition') or {}).get('prototypeMapExitTransitionImplemented'))} "
                    f"routeProgressFeedback={((default_map_exit_state.get('routeProgressFeedbackLast') or {}).get('source') or '')}:"
                    f"{((default_map_exit_state.get('routeProgressFeedbackLast') or {}).get('text') or '')} "
                    "trialTransitions='' "
                    "originalRoutePromotionImplemented=False"
                ),
                "recordedRoute": (
                    "map1_01a->map2_02d "
                    f"map={progress_state.get('map')} "
                    f"payloadMap={progress_state.get('payloadMap')} "
                    f"routeCandidateCount={(progress_state.get('progress') or {}).get('counts', {}).get('route-candidate')} "
                    f"autoSaved={(progress_state.get('autoSave') or {}).get('saved')} "
                    f"autoSource={(progress_state.get('autoSave') or {}).get('source')} "
                    f"routeProgressFeedback={((progress_state.get('routeProgressFeedbackLast') or {}).get('source') or '')}:"
                    f"{((progress_state.get('routeProgressFeedbackLast') or {}).get('text') or '')} "
                    f"routeProgressFeedbackRender={bool(progress_state.get('routeProgressFeedbackRender') or [])} "
                    f"blockReasons={((progress_state.get('routeProgressFeedbackLast') or {}).get('blockReasonCount') or 0)} "
                    f"trialOnly={((progress_state.get('routeProgressFeedbackLast') or {}).get('routeBlockerShortText') or '')} "
                    f"{route_progress_sound_summary(progress_state)} "
                    "pathProgress=후보 진행 1/2 "
                    "originalRoutePromotionImplemented=False"
                ),
                "restoredRoute": (
                    "map1_01a->map2_02d "
                    f"map={restore_state.get('map')} "
                    f"progressRestored={restore_state.get('loaded')} "
                    f"routeCandidateCount={(restore_state.get('progress') or {}).get('counts', {}).get('route-candidate')} "
                    "pathProgress=후보 진행 1/2 "
                    "originalRoutePromotionImplemented=False"
                ),
                "sourcePrompt": (
                    "map1_01a->map2_02d "
                    f"map={source_prompt_state.get('map')} "
                    f"routeCandidateCount={(source_prompt_state.get('progress') or {}).get('counts', {}).get('route-candidate')} "
                    f"prompt={(source_prompt_state.get('prompt') or {}).get('text')} "
                    f"blockReasons={((source_prompt_state.get('prompt') or {}).get('blockReasonCount') or 0)} "
                    f"trialOnly={((source_prompt_state.get('prompt') or {}).get('routeBlockerShortText') or '')} "
                    "completed=True "
                    "pathProgress=후보 완료 1/1 "
                    "originalRoutePromotionImplemented=False"
                ),
                "chainedRoute": (
                    "map1_01a->map2_02d->map2_18d "
                    f"map={chain_state.get('map')} "
                    f"payloadMap={chain_state.get('payloadMap')} "
                    f"routeCandidateCount={(chain_state.get('progress') or {}).get('counts', {}).get('route-candidate')} "
                    f"autoSaved={(chain_state.get('autoSave') or {}).get('saved')} "
                    f"autoSource={(chain_state.get('autoSave') or {}).get('source')} "
                    f"routeProgressFeedback={((chain_state.get('routeProgressFeedbackLast') or {}).get('source') or '')}:"
                    f"{((chain_state.get('routeProgressFeedbackLast') or {}).get('text') or '')} "
                    f"routeProgressFeedbackRender={bool(chain_state.get('routeProgressFeedbackRender') or [])} "
                    f"{route_progress_sound_summary(chain_state)} "
                    "pathProgress=후보 완료 2/2 "
                    "originalRoutePromotionImplemented=False"
                ),
                "chainedRestore": (
                    "map1_01a->map2_02d->map2_18d "
                    f"map={chain_restore_state.get('map')} "
                    f"progressRestored={chain_restore_state.get('loaded')} "
                    f"routeCandidateCount={(chain_restore_state.get('progress') or {}).get('counts', {}).get('route-candidate')} "
                    "pathProgress=후보 완료 2/2 "
                    "originalRoutePromotionImplemented=False"
                ),
                "titleRestoredRoute": (
                    "map1_01a->map2_02d->map2_18d "
                    f"titleContinue={title_continue_restore.get('titleContinue')} "
                    f"label={title_continue_label} "
                    f"map={title_continue_restore.get('map')}@{(title_continue_restore.get('tile') or {}).get('x')},{(title_continue_restore.get('tile') or {}).get('y')} "
                    f"quickLoadText={title_continue_restore.get('quickLoadText')} "
                    f"routeGoal={title_continue_restore.get('routePathValue') or title_continue_restore.get('selectedRouteGoal')} "
                    f"routeCandidateCount={(title_continue_restore.get('progress') or {}).get('counts', {}).get('route-candidate')} "
                    "pathProgress=후보 완료 2/2 "
                    "originalRoutePromotionImplemented=False"
                ),
                "snapshots": {
                    "defaultMapExit": default_map_exit_state,
                    "record": progress_state,
                    "sourcePrompt": source_prompt_state,
                    "restore": restore_state,
                    "chain": chain_state,
                    "chainRestore": chain_restore_state,
                    "continueTitle": continue_title,
                    "titleContinueClick": title_continue_click,
                    "continuedRouteMap": continued_route_map,
                    "titleContinueRestore": title_continue_restore,
                    "menuStart": menu_start_state,
                    "routeGoalMenu": route_goal_state,
                    "routeNextObjectiveAction": route_next_objective_action_state,
                    "routeContinuationObjectiveAction": route_continuation_objective_action_state,
                    "routeFieldEncounterObjectiveAction": route_field_objective_action_state,
                    "quickObjectiveKeyboard": quick_objective_keyboard_state,
                    "quickObjectiveToolbarNext": quick_objective_toolbar_next_state,
                    "quickObjectiveButtonNext": quick_objective_button_next_state,
                    "quickObjectiveButton": quick_objective_button_state,
                    "quickObjectiveToolbar": quick_objective_toolbar_state,
                    "routeNextContinuationButton": route_next_continuation_button_state,
                    "routeContinuationMenu": continuation_state,
                    "routeContinuationEntry": continuation_entry_state,
                    "routeContinuationChainMenu": continuation_chain_state,
                    "routeContinuationChainEntry": continuation_chain_entry_state,
                    "continuationContinueTitle": continuation_continue_title,
                    "continuationTitleContinueClick": continuation_title_click,
                    "routeContinuationTitleRestore": continuation_title_restore,
                    "routeContinuationRestoredMenu": continuation_restored_state,
                    "routeContinuationRestoredEntry": continuation_restored_entry_state,
                    "restoredContinueTitle": restored_continue_title,
                    "restoredTitleContinueClick": restored_title_click,
                    "routeContinuationRestoredTitleRestore": restored_title_restore,
                    "routeContinuationFinalMenu": continuation_final_state,
                    "routeContinuationFinalEntry": continuation_final_entry_state,
                    "finalContinueTitle": final_continue_title,
                    "finalTitleContinueClick": final_title_click,
                    "routeContinuationFinalTitleRestore": final_title_restore,
                    "routeContinuationFinalFieldEncounter": continuation_final_field_encounter,
                    "fieldEncounterContinueTitle": field_encounter_continue_title,
                    "fieldEncounterTitleContinueClick": field_encounter_title_click,
                    "routeContinuationFinalFieldEncounterTitleRestore": field_encounter_title_restore,
                    "routeCompletionNotice": route_completion_notice,
                    "completionGateContinueTitle": completion_gate_continue_title,
                    "completionGateTitleContinueClick": completion_gate_title_click,
                    "completionGateTitleRestore": completion_gate_title_restore,
                    "restoredCompletionMenuNotice": restored_completion_menu_notice,
                    "titleRouteCompletionGateTitle": title_route_completion_gate_title,
                    "titleRouteCompletionGate": title_route_completion_gate,
                    "titleRouteClearGateTitle": title_route_clear_gate_title,
                    "titleRouteClearGate": title_route_clear_gate,
                    "routeClearGateContinueTitle": route_clear_gate_continue_title,
                    "routeClearGateTitleContinueClick": route_clear_gate_title_click,
                    "routeClearGateTitleRestore": route_clear_gate_title_restore,
                    "routeClearControls": route_clear_controls,
                    "restoredRouteClearMenuSummary": restored_route_clear_menu_summary,
                },
            }
            write_report(report)
            print(
                "ok candidate route progress browser "
                f"menuStart={report['routeMenuStart']} goalMenu={report['routeGoalMenu']} "
                f"objectiveNext={report['routeNextObjectiveAction']} "
                f"objectiveContinuation={report['routeContinuationObjectiveAction']} "
                f"objectiveField={report['routeFieldEncounterObjectiveAction']} "
                f"quickObjectiveKeyboard={report['quickObjectiveKeyboard']} "
                f"quickObjectiveToolbarNext={report['quickObjectiveToolbarNext']} "
                f"quickObjectiveButtonNext={report['quickObjectiveButtonNext']} "
                f"quickObjectiveButton={report['quickObjectiveButton']} "
                f"quickObjectiveToolbar={report['quickObjectiveToolbar']} "
                f"continuationMenu={report['routeContinuationMenu']} "
                f"continuationEntry={report['routeContinuationEntry']} "
                f"continuationChainMenu={report['routeContinuationChainMenu']} "
                f"continuationChainEntry={report['routeContinuationChainEntry']} "
                f"continuationTitleRestore={report['routeContinuationTitleRestore']} "
                f"continuationRestoredMenu={report['routeContinuationRestoredMenu']} "
                f"continuationRestoredEntry={report['routeContinuationRestoredEntry']} "
                f"continuationRestoredTitleRestore={report['routeContinuationRestoredTitleRestore']} "
                f"continuationFinalMenu={report['routeContinuationFinalMenu']} "
                f"continuationFinalEntry={report['routeContinuationFinalEntry']} "
                f"continuationFinalTitleRestore={report['routeContinuationFinalTitleRestore']} "
                f"continuationFinalFieldEncounter={report['routeContinuationFinalFieldEncounter']} "
                f"continuationFinalFieldEncounterTitleRestore={report['routeContinuationFinalFieldEncounterTitleRestore']} "
                f"routeCompletionNotice={report['routeCompletionNotice']} "
                f"completionGateTitleRestore={report['completionGateTitleRestore']} "
                f"restoredCompletionMenuNotice={report['restoredCompletionMenuNotice']} "
                f"titleRouteCompletionGate={report['titleRouteCompletionGate']} "
                f"titleRouteClearGate={report['titleRouteClearGate']} "
                f"routeClearGateTitleRestore={report['routeClearGateTitleRestore']} "
                f"routeClearControls={report['routeClearControls']} "
                f"restoredRouteClearMenuSummary={report['restoredRouteClearMenuSummary']} "
                f"defaultMapExit={report['defaultMapExit']} "
                f"recorded={report['recordedRoute']} sourcePrompt={report['sourcePrompt']} "
                f"restored={report['restoredRoute']} "
                f"chained={report['chainedRoute']} chainRestored={report['chainedRestore']} "
                f"titleRestored={report['titleRestoredRoute']}"
            )
        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()
