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

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

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

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

EXPECTED_BATTLE_VM_REPLAY = {
    "event-dialogue-block-015": {
        "traceCommandCount": 16,
        "separatorEventCount": 10,
        "renderEventCount": 5,
        "literalTextEventCount": 2,
    },
    "event-dialogue-block-038": {
        "traceCommandCount": 12,
        "separatorEventCount": 8,
        "renderEventCount": 3,
        "literalTextEventCount": 2,
    },
    "event-dialogue-block-041": {
        "traceCommandCount": 14,
        "separatorEventCount": 8,
        "renderEventCount": 5,
        "literalTextEventCount": 8,
    },
}


def verify_battle_vm_replay(vm_replay: dict, expected_block_id: str) -> None:
    expected = EXPECTED_BATTLE_VM_REPLAY[expected_block_id]
    if (
        not isinstance(vm_replay, dict)
        or vm_replay.get("blockId") != expected_block_id
        or vm_replay.get("source") != "partial-opcode-replay"
        or vm_replay.get("traceCommandCount") != expected["traceCommandCount"]
        or vm_replay.get("executedOpcodeCount") != expected["traceCommandCount"]
        or vm_replay.get("separatorCommandCount") != expected["separatorEventCount"]
        or vm_replay.get("separatorEventCount") != expected["separatorEventCount"]
        or vm_replay.get("renderEventCount") != expected["renderEventCount"]
        or vm_replay.get("vmDrivenLineCount") != expected["renderEventCount"]
        or vm_replay.get("literalTextCommandCount") != expected["literalTextEventCount"]
        or vm_replay.get("literalTextEventCount") != expected["literalTextEventCount"]
        or vm_replay.get("renderCoverageStatus") != "partial-render-prefix"
        or vm_replay.get("firstRenderTextSourceValueHex") != "0x00030000"
        or vm_replay.get("routeLinkedEventVmExecution") is not False
        or vm_replay.get("browserEventVmPartialReplayImplemented") is not True
        or vm_replay.get("browserEventVmFullImplementation") is not False
        or vm_replay.get("originalEventVmRuntimeImplemented") is not False
        or vm_replay.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"battle VM replay is incomplete for {expected_block_id}: {vm_replay!r}")


def battle_vm_report(vm_replay: dict | None) -> str:
    row = vm_replay or {}
    return (
        f"vmBlocks={row.get('blockId') or ''} "
        f"vmPartial={row.get('browserEventVmPartialReplayImplemented')} "
        f"vmFull={row.get('browserEventVmFullImplementation')} "
        f"vmRender={row.get('renderEventCount')} "
        f"vmSeparator={row.get('separatorEventCount')} "
        f"vmLiteral={row.get('literalTextEventCount')} "
        f"vmSource={row.get('firstRenderTextSourceValueHex')}"
    )


def battle_enemy_action_text_report(summary: dict | None) -> str:
    row = summary or {}
    return (
        f"enemyActionNameSource={row.get('enemyActionNameSource')} "
        f"enemyActionTextTable={row.get('enemyActionTextTableKey')} "
        f"enemyActionTextIndex={row.get('enemyActionTextTableIndex')} "
        f"enemyActionTextRef={row.get('enemyActionTextTableRefVaHex')} "
        f"enemyActionTextVa={row.get('enemyActionTextTableTextVaHex')}"
    )


def sound_event_matches(row: dict | None, key: str, src_suffix: str) -> bool:
    event = row or {}
    return event.get("key") == key and str(event.get("src") or "").endswith(src_suffix)


def battle_start_feedback_sound_matches(row: dict | None) -> bool:
    entry = row or {}
    return (
        entry.get("battleStartSound") == "battleStart"
        and str(entry.get("battleStartSoundSrc") or "").endswith("/extract_wlk/08.wav")
        and entry.get("battleStartSoundPlayed") is True
    )


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 expected_battle_action_text_table(action_index: int) -> dict[str, str | int]:
    rows = {
        0: {"ref": "0x004d2974", "text": "0x004d42f6"},
        1: {"ref": "0x004d297c", "text": "0x004d4314"},
        3: {"ref": "0x004d29a4", "text": "0x004d438c"},
        8: {"ref": "0x004d29e4", "text": "0x004d447c"},
        12: {"ref": "0x004d2a0c", "text": "0x004d4512"},
    }
    row = rows.get(action_index, {"ref": "", "text": ""})
    return {
        "key": "battleCommands",
        "index": action_index,
        "refVaHex": row["ref"],
        "textVaHex": row["text"],
    }


def verify_battle_command_text_provenance(row: dict, action_index: int, *, prefix: str = "") -> None:
    expected = expected_battle_action_text_table(action_index)

    def key(name: str) -> str:
        return f"{prefix}{name[:1].upper()}{name[1:]}" if prefix else name

    expected_source = "exe-text-table-battleCommands"
    if (
        row.get(key("nameSource")) != expected_source
        or row.get(key("textTableKey")) != expected["key"]
        or row.get(key("textTableIndex")) != expected["index"]
        or row.get(key("textTableRefVaHex")) != expected["refVaHex"]
        or row.get(key("textTableTextVaHex")) != expected["textVaHex"]
    ):
        raise WebDriverError(
            f"battle command text table provenance mismatch for battleCommands[{action_index}]: {row!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 prepare_battle_button_script() -> str:
    return """
window.__hwanseCandidateBattleButton = null;
ensureBattleData().then(() => {
  updateBattleButton();
  const button = document.getElementById('battleButton');
  window.__hwanseCandidateBattleButton = {
    hidden: button?.hidden ?? null,
    text: button?.textContent || '',
    title: button?.title || '',
    map: map?.name || '',
  };
}).catch((error) => {
  window.__hwanseCandidateBattleButton = { error: String(error && error.message || error) };
});
return true;
"""


def battle_button_state_script() -> str:
    return "return window.__hwanseCandidateBattleButton || null;"


def click_battle_button_script() -> str:
    return """
const button = document.getElementById('battleButton');
const before = {
  hidden: button?.hidden ?? null,
  text: button?.textContent || '',
  title: button?.title || '',
};
button?.click();
return { ok: !!button, before };
"""


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


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),
    titleMenuLabels: items.map((item) => item.label),
  };
}
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),
  titleMenuLabels: items.map((item) => item.label),
  selectedTitleMenuIndex,
};
"""


def battle_prototype_state_script() -> str:
    return """
return {
  scene,
  summary: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
  battleCandidate: battleState?.candidate ? {
    id: battleState.candidate.id || '',
    blockId: battleState.candidate.blockId || '',
    battleBackground: battleState.candidate.battleBackground || '',
    sampleText: battleState.candidate.sampleText || '',
  } : null,
  background: battleState?.background?.name || '',
  enemyName: battleState?.enemy?.name || '',
  enemyHp: battleState?.enemy?.hp ?? null,
  enemyHpMax: battleState?.enemy?.hpMax ?? null,
  enemyAtk: battleState?.enemy?.atk ?? null,
  enemyDef: battleState?.enemy?.def ?? null,
  enemyAction: battleState?.enemy?.action || null,
  enemyProfile: battleState?.enemy?.profile || null,
      enemyProfileSource: battleState?.enemy?.profileSource || '',
      enemyVisual: battleState?.enemy?.visual || null,
  enemyOriginalEnemyRowBound: battleState?.enemy?.originalEnemyRowBound ?? null,
  enemyOriginalStatsOrRewardsBound: battleState?.enemy?.originalStatsOrRewardsBound ?? null,
  log: battleState?.log || [],
  names: (battleState?.party || []).map((member) => member.name),
  commands: battleCommandItems().map((command) => command.name),
};
"""


def battle_pointer_attack_script() -> str:
    return """
window.HWANSE_LAST_BATTLE_HIT_EFFECT = null;
window.HWANSE_LAST_BATTLE_HIT_EFFECT_RENDER = null;
window.HWANSE_BATTLE_HIT_EFFECT_LOG = [];
window.HWANSE_LAST_BATTLE_DAMAGE_TEXT = null;
window.HWANSE_LAST_BATTLE_DAMAGE_TEXT_RENDER = null;
window.HWANSE_BATTLE_DAMAGE_TEXT_LOG = [];
window.HWANSE_BATTLE_DAMAGE_TEXT_RENDER = [];
window.HWANSE_LAST_BATTLE_PARTY_ACTION_ANIMATION = null;
window.HWANSE_LAST_BATTLE_PARTY_ACTION_ANIMATION_RENDER = null;
window.HWANSE_BATTLE_PARTY_ACTION_ANIMATION_LOG = [];
window.HWANSE_LAST_BATTLE_ENEMY_ATTACK_ANIMATION = null;
window.HWANSE_LAST_BATTLE_ENEMY_ATTACK_ANIMATION_RENDER = null;
window.HWANSE_BATTLE_ENEMY_ATTACK_ANIMATION_LOG = [];
window.HWANSE_LAST_BATTLE_TURN_RESULT = null;
window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE = null;
window.HWANSE_SOUND_COUNTS = {};
window.HWANSE_SOUND_LOG = [];
window.HWANSE_LAST_SOUND = null;
if (typeof activeBattleDamageTexts !== 'undefined') activeBattleDamageTexts = [];
if (typeof activeBattlePartyActionAnimation !== 'undefined') activeBattlePartyActionAnimation = null;
if (typeof activeBattleEnemyAttackAnimation !== 'undefined') activeBattleEnemyAttackAnimation = null;
const choices = battleHudChoiceItems();
const attackIndex = choices.findIndex((choice) => choice.key === 'attack');
if (attackIndex < 0) {
  return {
    ok: false,
    reason: 'missing-attack',
    choices: choices.map((choice) => choice.name || choice.key || ''),
  };
}
battleState.selectedCommandIndex = attackIndex;
const windowInfo = battleHudChoiceWindow(choices);
const visibleIndex = attackIndex - windowInfo.start;
const geometry = BATTLE_HUD_GEOMETRY;
const canvas = document.getElementById('screen');
const rect = canvas.getBoundingClientRect();
const col = visibleIndex % 2;
const row = Math.floor(visibleIndex / 2);
const targetX = geometry.commandX + col * geometry.colWidth + geometry.commandHitWidth / 2 - 8;
const targetY = geometry.commandY + row * geometry.commandRowHeight + geometry.commandHitHeight / 2 - 2;
const clientX = rect.left + (targetX / canvas.width) * rect.width;
const clientY = rect.top + (targetY / canvas.height) * rect.height;
const beforeHp = battleState?.enemy?.hp ?? null;
const hitIndex = battleHudChoiceIndexAtPoint(clientX, clientY);
canvas.dispatchEvent(new PointerEvent('pointerdown', {
  bubbles: true,
  cancelable: true,
  pointerId: 41,
  pointerType: 'mouse',
  isPrimary: true,
  clientX,
  clientY,
}));
if (typeof render === 'function') render();
const afterHp = battleState?.enemy?.hp ?? null;
return {
  ok: hitIndex === attackIndex && afterHp !== null && beforeHp !== null && afterHp < beforeHp,
  scene,
  attackIndex,
  hitIndex,
  beforeHp,
  afterHp,
  windowInfo,
  selectedCommandIndex: battleState?.selectedCommandIndex ?? null,
  activeActorIndex: battleState?.activeActorIndex ?? null,
  log: battleState?.log || [],
  hitEffect: window.HWANSE_LAST_BATTLE_HIT_EFFECT || null,
  hitEffectRender: window.HWANSE_LAST_BATTLE_HIT_EFFECT_RENDER || null,
  hitEffectLog: window.HWANSE_BATTLE_HIT_EFFECT_LOG || [],
  damageText: window.HWANSE_LAST_BATTLE_DAMAGE_TEXT || null,
  damageTextRender: window.HWANSE_BATTLE_DAMAGE_TEXT_RENDER || [],
  damageTextLog: window.HWANSE_BATTLE_DAMAGE_TEXT_LOG || [],
  partyActionAnimation: window.HWANSE_LAST_BATTLE_PARTY_ACTION_ANIMATION || null,
  partyActionAnimationRender: window.HWANSE_LAST_BATTLE_PARTY_ACTION_ANIMATION_RENDER || null,
  partyActionAnimationLog: window.HWANSE_BATTLE_PARTY_ACTION_ANIMATION_LOG || [],
  enemyAttackAnimation: window.HWANSE_LAST_BATTLE_ENEMY_ATTACK_ANIMATION || null,
  enemyAttackAnimationRender: window.HWANSE_LAST_BATTLE_ENEMY_ATTACK_ANIMATION_RENDER || null,
  enemyAttackAnimationLog: window.HWANSE_BATTLE_ENEMY_ATTACK_ANIMATION_LOG || [],
  enemyAttackReview: window.HWANSE_LAST_BATTLE_ENEMY_ATTACK_REVIEW || null,
  battleReviewLinks: window.HWANSE_LAST_BATTLE_REVIEW_LINKS || null,
  battleReviewHref: document.getElementById('battleReviewLink')?.getAttribute('href') || '',
  monsterReviewHref: document.getElementById('monsterReviewLink')?.getAttribute('href') || '',
  turnResult: window.HWANSE_LAST_BATTLE_TURN_RESULT || null,
  turnAutoSave: window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE || null,
  battlePrototype: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
  soundState: {
    counts: { ...(window.HWANSE_SOUND_COUNTS || {}) },
    last: window.HWANSE_LAST_SOUND || null,
    log: Array.isArray(window.HWANSE_SOUND_LOG) ? [...window.HWANSE_SOUND_LOG] : [],
  },
};
"""


def battle_skill_action_script() -> str:
    return """
window.__hwanseBattleSkillAction = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
runtimeState = createPrototypeRuntimeState();
const learnedSkillSummary = (value, skillIndex = 0) => ({
  individual: {
    learned: 1,
    total: 1,
    maxLevel: 1,
    slots: [skillIndex],
    entries: [
      { value, valueHex: `0x${value.toString(16).padStart(2, '0')}`, learned: true, level: 1, slot: skillIndex, skillIndex },
    ],
  },
  group: {
    learned: 0,
    total: 1,
    maxLevel: 0,
    slots: [0],
    entries: [
      { value: 0x00, valueHex: '0x00', learned: false, level: 0, slot: 0, skillIndex: 0 },
    ],
  },
});
const battleSkillMember = (name, mp, skillValue) => {
  const member = defaultBattleMember(name);
  member.hp = Math.max(96, Number(member.hp || 0));
  member.hpMax = Math.max(96, Number(member.hpMax || 0));
  member.mp = mp;
  member.mpMax = mp;
  member.skillSummary = learnedSkillSummary(skillValue, 0);
  member.statuses = [];
  member.prototypeStateSource = 'battle-party-skill-frame-smoke';
  return member;
};
runtimeState.characters = [
  battleSkillMember('Ataho', 8, 0x0a),
  battleSkillMember('Rinshan', 14, 0x09),
  battleSkillMember('Smashu', 4, 0x05),
];
runtimeState.items = [];
const resetSkillActionMarkers = () => {
  window.HWANSE_LAST_BATTLE_HIT_EFFECT = null;
  window.HWANSE_LAST_BATTLE_HIT_EFFECT_RENDER = null;
  window.HWANSE_BATTLE_HIT_EFFECT_LOG = [];
  window.HWANSE_LAST_BATTLE_DAMAGE_TEXT = null;
  window.HWANSE_LAST_BATTLE_DAMAGE_TEXT_RENDER = null;
  window.HWANSE_BATTLE_DAMAGE_TEXT_LOG = [];
  window.HWANSE_BATTLE_DAMAGE_TEXT_RENDER = [];
  window.HWANSE_LAST_BATTLE_PARTY_ACTION_ANIMATION = null;
  window.HWANSE_LAST_BATTLE_PARTY_ACTION_ANIMATION_RENDER = null;
  window.HWANSE_BATTLE_PARTY_ACTION_ANIMATION_LOG = [];
  window.HWANSE_LAST_BATTLE_SKILL_USE = null;
  window.HWANSE_LAST_BATTLE_SKILL_USE_AUTO_SAVE = null;
  window.HWANSE_LAST_BATTLE_TURN_RESULT = null;
  window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE = null;
  window.HWANSE_SOUND_COUNTS = {};
  window.HWANSE_SOUND_LOG = [];
  window.HWANSE_LAST_SOUND = null;
  if (typeof activeBattleDamageTexts !== 'undefined') activeBattleDamageTexts = [];
  if (typeof activeBattlePartyActionAnimation !== 'undefined') activeBattlePartyActionAnimation = null;
};
resetSkillActionMarkers();
Promise.resolve()
  .then(() => ensureBattleData())
  .then(() => startBattlePrototype())
  .then(() => {
    const runMemberSkill = (memberName, enemyHpBefore) => {
      resetSkillActionMarkers();
      const memberIndex = (battleState?.party || []).findIndex((member) => member.name === memberName);
      if (memberIndex < 0) {
        return { ok: false, reason: `missing-member-${memberName}` };
      }
      const actor = battleState.party[memberIndex];
      battleState.finished = false;
      battleState.activeActorIndex = memberIndex;
      battleState.enemy.hp = enemyHpBefore;
      battleState.enemy.hpMax = Math.max(Number(battleState.enemy.hpMax || 0), enemyHpBefore);
      const commands = battleCommandItems(actor);
      const skillIndex = commands.findIndex((command) => command.kind === 'skill');
      if (skillIndex < 0) {
        return {
          ok: false,
          reason: 'missing-skill-command',
          memberName,
          commands: commands.map((command) => ({
            key: command.key || '',
            kind: command.kind || '',
            name: command.name || '',
          })),
        };
      }
      const skillCommand = commands[skillIndex];
      battleState.selectedCommandIndex = skillIndex;
      const beforeHp = Math.max(0, Number(battleState?.enemy?.hp || 0));
      const beforeMp = Math.max(0, Number(actor?.mp || 0));
      const commandResult = useSelectedBattleCommand();
      if (typeof render === 'function') render();
      const afterHp = Math.max(0, Number(battleState?.enemy?.hp || 0));
      const afterMp = Math.max(0, Number(actor?.mp || 0));
      return {
        ok: commandResult === true && afterHp < beforeHp && afterMp < beforeMp,
        memberName,
        skillIndex,
        commandResult,
        skillCommand,
        beforeHp,
        afterHp,
        beforeMp,
        afterMp,
        selectedCommandIndex: battleState?.selectedCommandIndex ?? null,
        activeActorIndex: battleState?.activeActorIndex ?? null,
        hitEffect: window.HWANSE_LAST_BATTLE_HIT_EFFECT || null,
        hitEffectRender: window.HWANSE_LAST_BATTLE_HIT_EFFECT_RENDER || null,
        hitEffectLog: window.HWANSE_BATTLE_HIT_EFFECT_LOG || [],
        damageText: window.HWANSE_LAST_BATTLE_DAMAGE_TEXT || null,
        damageTextRender: window.HWANSE_BATTLE_DAMAGE_TEXT_RENDER || [],
        damageTextLog: window.HWANSE_BATTLE_DAMAGE_TEXT_LOG || [],
        partyActionAnimation: window.HWANSE_LAST_BATTLE_PARTY_ACTION_ANIMATION || null,
        partyActionAnimationRender: window.HWANSE_LAST_BATTLE_PARTY_ACTION_ANIMATION_RENDER || null,
        partyActionAnimationLog: window.HWANSE_BATTLE_PARTY_ACTION_ANIMATION_LOG || [],
        skillUse: window.HWANSE_LAST_BATTLE_SKILL_USE || null,
        skillAutoSave: window.HWANSE_LAST_BATTLE_SKILL_USE_AUTO_SAVE || null,
        turnResult: window.HWANSE_LAST_BATTLE_TURN_RESULT || null,
        turnAutoSave: window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE || null,
        battlePrototype: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
        soundState: {
          counts: { ...(window.HWANSE_SOUND_COUNTS || {}) },
          last: window.HWANSE_LAST_SOUND || null,
          log: Array.isArray(window.HWANSE_SOUND_LOG) ? [...window.HWANSE_SOUND_LOG] : [],
        },
      };
    };
    const partySkillFrames = [
      runMemberSkill('Ataho', 42),
      runMemberSkill('Rinshan', 100),
      runMemberSkill('Smashu', 100),
    ];
    const primary = partySkillFrames[0] || {};
    let savedPayload = null;
    try {
      savedPayload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || 'null');
    } catch (error) {
      savedPayload = { error: String(error && error.message || error) };
    }
    window.__hwanseBattleSkillAction = {
      ok: partySkillFrames.every((entry) => entry.ok === true),
      scene,
      map: map?.name || '',
      partySkillFrames,
      skillIndex: primary.skillIndex,
      commandResult: primary.commandResult,
      skillCommand: primary.skillCommand,
      beforeHp: primary.beforeHp,
      afterHp: primary.afterHp,
      beforeMp: primary.beforeMp,
      afterMp: primary.afterMp,
      actorName: primary.memberName || '',
      selectedCommandIndex: primary.selectedCommandIndex ?? null,
      activeActorIndex: primary.activeActorIndex ?? null,
      log: battleState?.log || [],
      hitEffect: primary.hitEffect || null,
      hitEffectRender: primary.hitEffectRender || null,
      hitEffectLog: primary.hitEffectLog || [],
      damageText: primary.damageText || null,
      damageTextRender: primary.damageTextRender || [],
      damageTextLog: primary.damageTextLog || [],
      partyActionAnimation: primary.partyActionAnimation || null,
      partyActionAnimationRender: primary.partyActionAnimationRender || null,
      partyActionAnimationLog: primary.partyActionAnimationLog || [],
      skillUse: primary.skillUse || null,
      skillAutoSave: primary.skillAutoSave || null,
      turnResult: primary.turnResult || null,
      turnAutoSave: primary.turnAutoSave || null,
      battlePrototype: primary.battlePrototype || null,
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      savedPayload,
      soundState: primary.soundState || null,
    };
  })
  .catch((error) => {
    window.__hwanseBattleSkillAction = { ok: false, error: String(error && error.message || error) };
  });
return true;
"""


def battle_skill_action_state_script() -> str:
    return "return window.__hwanseBattleSkillAction || null;"


def battle_item_target_selection_script() -> str:
    return """
window.__hwanseBattleItemTargetSelection = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
runtimeState = createPrototypeRuntimeState();
runtimeState.items = [
  { key: 'herb', name: saveItemName('herb', '약초'), count: 2 },
  { key: 'item_2', name: saveItemName('item_2', '해독초'), count: 0 },
  { key: 'refresh_water', name: saveItemName('refresh_water', '활신수'), count: 0 },
  { key: 'mp_recovery', name: saveItemName('mp_recovery', '마법의 물약'), count: 0 },
  { key: 'item_5', name: saveItemName('item_5', '영약'), count: 0 },
  { key: 'item_6', name: saveItemName('item_6', '마수석'), count: 0 },
];
runtimeState.characters = ['Ataho', 'Rinshan', 'Smashu'].map((name) => ({
  ...defaultBattleMember(name),
  statuses: [],
  prototypeStateSource: 'battle-item-target-selection-smoke',
}));
runtimeState.characters[1].hp = 8;
runtimeState.characters[2].hp = 42;
window.HWANSE_BATTLE_ITEM_TARGET_SELECTION_LOG = [];
window.HWANSE_LAST_BATTLE_ITEM_TARGET_SELECTION = null;
window.HWANSE_LAST_BATTLE_ITEM_USE = null;
window.HWANSE_LAST_BATTLE_ITEM_USE_AUTO_SAVE = null;
window.HWANSE_LAST_BATTLE_TURN_RESULT = null;
window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE = null;
ensureBattleData()
  .then(() => startBattlePrototype())
  .then(() => {
    const actor = currentBattleActor();
    const commands = battleCommandItems(actor);
    const itemCommandIndex = commands.findIndex((choice) => choice.key === 'item');
    if (itemCommandIndex < 0) throw new Error('missing battle item command');
    battleState.selectedCommandIndex = itemCommandIndex;
    const beforeParty = (battleState.party || []).map((member) => ({
      name: member.name,
      hp: member.hp,
      hpMax: member.hpMax,
      mp: member.mp,
      mpMax: member.mpMax,
    }));
    const openResult = useSelectedBattleCommand();
    const targetChoices = battleHudChoiceItems().map((choice) => ({
      name: choice.name,
      memberName: choice.member?.name || '',
      usable: choice.usable === true,
      source: choice.source || '',
    }));
    const targetIndex = targetChoices.findIndex((choice) => choice.memberName === 'Rinshan');
    if (targetIndex < 0) throw new Error('missing Rinshan target choice');
    battleState.selectedTargetIndex = targetIndex;
    const openMarker = window.HWANSE_LAST_BATTLE_ITEM_TARGET_SELECTION || null;
    const selectResult = useSelectedBattleCommand();
    if (typeof render === 'function') render();
    const selectedMarker = window.HWANSE_LAST_BATTLE_ITEM_TARGET_SELECTION || null;
    const selectionLog = window.HWANSE_BATTLE_ITEM_TARGET_SELECTION_LOG || [];
    const itemUse = window.HWANSE_LAST_BATTLE_ITEM_USE || null;
    const itemAutoSave = window.HWANSE_LAST_BATTLE_ITEM_USE_AUTO_SAVE || null;
    const turnResult = window.HWANSE_LAST_BATTLE_TURN_RESULT || null;
    const turnAutoSave = window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE || null;
    let savedPayload = null;
    try {
      savedPayload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
    } catch (error) {
      savedPayload = { error: String(error && error.message || error) };
    }
    const afterParty = (battleState?.party || []).map((member) => ({
      name: member.name,
      hp: member.hp,
      hpMax: member.hpMax,
      mp: member.mp,
      mpMax: member.mpMax,
    }));
    const runtimeCharacters = (runtimeState?.characters || []).map((member) => ({
      name: member.name,
      hp: member.hp,
      hpMax: member.hpMax,
      mp: member.mp,
      mpMax: member.mpMax,
    }));
    const runtimeItems = (runtimeState?.items || []).map((item) => ({
      key: item.key,
      name: item.name,
      count: item.count || 0,
    }));
    if (typeof activeDialogue !== 'undefined') activeDialogue = null;
    window.HWANSE_BATTLE_ITEM_FEEDBACK_LOG = [];
    window.HWANSE_BATTLE_ITEM_FEEDBACK_RENDER = [];
    window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK = null;
    window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK_RENDER = null;
    window.HWANSE_SOUND_COUNTS = {};
    window.HWANSE_SOUND_LOG = [];
    window.HWANSE_LAST_SOUND = null;
    if (typeof activeBattleItemFeedbacks !== 'undefined') activeBattleItemFeedbacks = [];
    window.HWANSE_LAST_OBJECTIVE_ACTION = null;
    window.HWANSE_LAST_BATTLE_ITEM_COMPLETION_NOTICE = null;
    const objectiveBefore = typeof prototypeObjectiveState === 'function' ? prototypeObjectiveState() : null;
    const objectiveResult = typeof activatePrototypeObjectiveAction === 'function'
      ? activatePrototypeObjectiveAction()
      : false;
    if (typeof render === 'function') render();
    const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
    const objectiveCompletionNotice = window.HWANSE_LAST_BATTLE_ITEM_COMPLETION_NOTICE || null;
    const objectiveItemFeedbackLog = window.HWANSE_BATTLE_ITEM_FEEDBACK_LOG || [];
    const objectiveItemFeedbackRender = window.HWANSE_BATTLE_ITEM_FEEDBACK_RENDER || [];
    const objectiveItemFeedbackLast = window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK || null;
    const objectiveItemFeedbackLastRender = window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK_RENDER || null;
    const objectiveSoundState = {
      counts: { ...(window.HWANSE_SOUND_COUNTS || {}) },
      last: window.HWANSE_LAST_SOUND || null,
      log: Array.isArray(window.HWANSE_SOUND_LOG) ? [...window.HWANSE_SOUND_LOG] : [],
    };
    const objectiveActiveDialogueBlock = activeDialogue
      ? {
          blockId: activeDialogue.block?.blockId || '',
          line: activeDialogue.lines?.[activeDialogue.index || 0] || '',
          lineCount: activeDialogue.lines?.length || 0,
        }
      : null;
    window.__hwanseBattleItemTargetSelection = {
      ok: true,
      scene,
      map: map?.name || '',
      openResult,
      selectResult,
      itemCommandIndex,
      targetIndex,
      actorName: actor?.name || '',
      targetChoices,
      openMarker,
      selectedMarker,
      selectionLog,
      itemUse,
      itemAutoSave,
      turnResult,
      turnAutoSave,
      beforeParty,
      afterParty,
      runtimeCharacters,
      runtimeItems,
      savedPayload,
      battleMenuState: {
        itemMenuOpen: battleState?.itemMenuOpen === true,
        targetMenuOpen: battleState?.targetMenuOpen === true,
        pendingBattleItem: battleState?.pendingBattleItem || null,
        pendingBattleTarget: battleState?.pendingBattleTarget || null,
        activeActorIndex: battleState?.activeActorIndex ?? null,
      },
      log: battleState?.log || [],
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      objectiveBefore,
      objectiveResult,
      objectiveAction,
      objectiveCompletionNotice,
      objectiveActiveDialogueBlock,
      objectiveItemFeedbackLog,
      objectiveItemFeedbackRender,
      objectiveItemFeedbackLast,
      objectiveItemFeedbackLastRender,
      objectiveSoundState,
      originalItemEffectFormulaImplemented: false,
      originalStoryFlagRuntimeImplemented: false,
    };
  })
  .catch((error) => {
    window.__hwanseBattleItemTargetSelection = { error: String(error && error.message || error) };
  });
return true;
"""


def battle_item_target_selection_state_script() -> str:
    return "return window.__hwanseBattleItemTargetSelection || null;"


def start_battle_action_script() -> str:
    return """
window.__hwanseCandidateBattleAction = null;
window.HWANSE_BATTLE_START_FEEDBACK_LOG = [];
window.HWANSE_BATTLE_START_FEEDBACK_RENDER = [];
window.HWANSE_LAST_BATTLE_START_FEEDBACK = null;
window.HWANSE_LAST_BATTLE_START_FEEDBACK_RENDER = null;
window.HWANSE_LAST_DIALOGUE_BATTLE_LINK_ACTION = null;
if (typeof activeBattleStartFeedbacks !== 'undefined') activeBattleStartFeedbacks = [];
Promise.all([ensureSceneEvents(), ensureSavePointCandidates(), ensureEventDialogueBlocks(), ensureBattleData()])
  .then(() => {
    let tileX = 20;
    let tileY = 20;
    for (let y = 0; y < map.height; y += 1) {
      for (let x = 0; x < map.width; x += 1) {
        setPlayerPosition(x * map.tileSize - 16, y * map.tileSize - 31);
        publishPrototypeCompletionState();
        drawActionPrompt();
        if ((window.HWANSE_LAST_ACTION_PROMPT || {}).kind === 'battle-candidate') {
          tileX = x;
          tileY = y;
          y = map.height;
          break;
        }
      }
    }
    setPlayerPosition(tileX * map.tileSize - 16, tileY * map.tileSize - 31);
    publishPrototypeCompletionState();
    drawActionPrompt();
    const promptBefore = window.HWANSE_LAST_ACTION_PROMPT || null;
    const actionResult = activateHotspotAtFoot();
    window.__hwanseCandidateBattleAction = {
      actionResult,
      promptBefore,
      map: map?.name || '',
      tile: footTile(),
    };
  })
  .catch((error) => {
    window.__hwanseCandidateBattleAction = { error: String(error && error.message || error) };
  });
return true;
"""


def battle_action_marker_script() -> str:
    return "return window.__hwanseCandidateBattleAction || null;"


def battle_action_state_script() -> str:
    return """
if (typeof render === 'function') render();
return {
  marker: window.__hwanseCandidateBattleAction || null,
  scene,
  summary: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
  battleCandidate: battleState?.candidate ? {
    id: battleState.candidate.id || '',
    blockId: battleState.candidate.blockId || '',
    battleBackground: battleState.candidate.battleBackground || '',
    sampleText: battleState.candidate.sampleText || '',
  } : null,
  background: battleState?.background?.name || '',
  enemyName: battleState?.enemy?.name || '',
  enemyHp: battleState?.enemy?.hp ?? null,
  enemyHpMax: battleState?.enemy?.hpMax ?? null,
  enemyAtk: battleState?.enemy?.atk ?? null,
  enemyDef: battleState?.enemy?.def ?? null,
  enemyAction: battleState?.enemy?.action || null,
  enemyProfile: battleState?.enemy?.profile || null,
      enemyProfileSource: battleState?.enemy?.profileSource || '',
      enemyVisual: battleState?.enemy?.visual || null,
  enemyOriginalEnemyRowBound: battleState?.enemy?.originalEnemyRowBound ?? null,
  enemyOriginalStatsOrRewardsBound: battleState?.enemy?.originalStatsOrRewardsBound ?? null,
  log: battleState?.log || [],
  progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
  battleStartFeedbackLog: window.HWANSE_BATTLE_START_FEEDBACK_LOG || [],
  battleStartFeedbackRender: window.HWANSE_BATTLE_START_FEEDBACK_RENDER || [],
  battleStartFeedbackLast: window.HWANSE_LAST_BATTLE_START_FEEDBACK || null,
  battleStartFeedbackLastRender: window.HWANSE_LAST_BATTLE_START_FEEDBACK_RENDER || null,
};
"""


def battle_candidate_menu_selection_script() -> str:
    return """
window.__hwanseBattleCandidateMenuSelection = null;
Promise.resolve()
  .then(() => ensureBattleData())
  .then(() => openBattleCandidateMenu())
  .then(() => {
    const items = typeof battleCandidateMenuItems === 'function' ? battleCandidateMenuItems() : [];
    const labels = items.map((item) => item.name || '');
    const targetIndex = items.findIndex((item) => item.battleCandidate?.battleBackground === 'btl_j1');
    if (targetIndex < 0) {
      window.__hwanseBattleCandidateMenuSelection = {
        ok: false,
        error: 'missing btl_j1',
        menuMode,
        labels,
        count: items.length,
        menuMarker: window.HWANSE_LAST_BATTLE_CANDIDATE_MENU || null,
      };
      return;
    }
    const target = items[targetIndex];
    const before = {
      menuMode,
      notice: menuNotice,
      selectedMenuItemIndex,
      labels,
      count: items.length,
      targetIndex,
      targetName: target.name || '',
      targetCandidateId: target.battleCandidate?.id || '',
      targetBackground: target.battleCandidate?.battleBackground || '',
      targetEnemy: target.enemyName || '',
      targetSprite: target.enemySprite || '',
      menuMarker: window.HWANSE_LAST_BATTLE_CANDIDATE_MENU || null,
    };
    selectedMenuItemIndex = targetIndex;
    const commandResult = useSelectedMenuItem();
    window.__hwanseBattleCandidateMenuSelection = {
      ok: true,
      before,
      commandResult,
      afterMenuMode: menuMode,
      afterMenuOpen: menuOpen,
      selection: window.HWANSE_LAST_BATTLE_CANDIDATE_MENU_SELECTION || null,
    };
  })
  .catch((error) => {
    window.__hwanseBattleCandidateMenuSelection = { ok: false, error: String(error && error.message || error) };
  });
return true;
"""


def battle_candidate_menu_state_script() -> str:
    return """
if (typeof render === 'function') render();
return {
  marker: window.__hwanseBattleCandidateMenuSelection || null,
  menuMarker: window.HWANSE_LAST_BATTLE_CANDIDATE_MENU || null,
  selection: window.HWANSE_LAST_BATTLE_CANDIDATE_MENU_SELECTION || null,
  scene,
  summary: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
  battleCandidate: battleState?.candidate ? {
    id: battleState.candidate.id || '',
    blockId: battleState.candidate.blockId || '',
    battleBackground: battleState.candidate.battleBackground || '',
  } : null,
  background: battleState?.background?.name || '',
  enemyName: battleState?.enemy?.name || '',
  enemyProfile: battleState?.enemy?.profile || null,
  enemyVisual: battleState?.enemy?.visual || null,
  battleStartFeedbackLog: window.HWANSE_BATTLE_START_FEEDBACK_LOG || [],
  battleStartFeedbackLast: window.HWANSE_LAST_BATTLE_START_FEEDBACK || null,
  battleStartFeedbackRender: window.HWANSE_BATTLE_START_FEEDBACK_RENDER || [],
  battleStartFeedbackLastRender: window.HWANSE_LAST_BATTLE_START_FEEDBACK_RENDER || null,
};
"""


def battle_sprite_menu_selection_script() -> str:
    return """
window.__hwanseBattleSpriteMenuSelection = null;
Promise.resolve()
  .then(() => ensureBattleData())
  .then(() => openBattleCandidateMenu())
  .then(() => {
    const items = typeof battleCandidateMenuItems === 'function' ? battleCandidateMenuItems() : [];
    const labels = items.map((item) => item.name || '');
    const targetIndex = items.findIndex((item) => item.battleCandidate?.enemyAssetKey === 'zk_big');
    if (targetIndex < 0) {
      window.__hwanseBattleSpriteMenuSelection = {
        ok: false,
        error: 'missing zk_big',
        menuMode,
        labels,
        count: items.length,
        menuMarker: window.HWANSE_LAST_BATTLE_CANDIDATE_MENU || null,
      };
      return;
    }
    const target = items[targetIndex];
    const candidate = target.battleCandidate || {};
    const before = {
      menuMode,
      notice: menuNotice,
      selectedMenuItemIndex,
      labels,
      count: items.length,
      targetIndex,
      targetName: target.name || '',
      targetCandidateId: candidate.id || '',
      targetBackground: candidate.battleBackground || '',
      targetEnemy: target.enemyName || '',
      targetSprite: target.enemySprite || '',
      targetAsset: candidate.enemyAssetKey || '',
      targetSource: target.source || '',
      menuMarker: window.HWANSE_LAST_BATTLE_CANDIDATE_MENU || null,
    };
    selectedMenuItemIndex = targetIndex;
    const commandResult = useSelectedMenuItem();
    window.__hwanseBattleSpriteMenuSelection = {
      ok: true,
      before,
      commandResult,
      afterMenuMode: menuMode,
      afterMenuOpen: menuOpen,
      selection: window.HWANSE_LAST_BATTLE_CANDIDATE_MENU_SELECTION || null,
    };
  })
  .catch((error) => {
    window.__hwanseBattleSpriteMenuSelection = { ok: false, error: String(error && error.message || error) };
  });
return true;
"""


def battle_sprite_menu_state_script() -> str:
    return """
if (typeof render === 'function') render();
return {
  marker: window.__hwanseBattleSpriteMenuSelection || null,
  menuMarker: window.HWANSE_LAST_BATTLE_CANDIDATE_MENU || null,
  selection: window.HWANSE_LAST_BATTLE_CANDIDATE_MENU_SELECTION || null,
  scene,
  summary: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
  battleCandidate: battleState?.candidate ? {
    id: battleState.candidate.id || '',
    blockId: battleState.candidate.blockId || '',
    battleBackground: battleState.candidate.battleBackground || '',
    enemySpriteOnly: battleState.candidate.enemySpriteOnly === true,
    enemyCns: battleState.candidate.enemyCns || '',
    enemyAssetKey: battleState.candidate.enemyAssetKey || '',
  } : null,
  background: battleState?.background?.name || '',
  enemyName: battleState?.enemy?.name || '',
  enemyProfile: battleState?.enemy?.profile || null,
  enemyVisual: battleState?.enemy?.visual || null,
  battleStartFeedbackLog: window.HWANSE_BATTLE_START_FEEDBACK_LOG || [],
  battleStartFeedbackLast: window.HWANSE_LAST_BATTLE_START_FEEDBACK || null,
  battleStartFeedbackRender: window.HWANSE_BATTLE_START_FEEDBACK_RENDER || [],
  battleStartFeedbackLastRender: window.HWANSE_LAST_BATTLE_START_FEEDBACK_RENDER || null,
};
"""


def battle_sprite_direct_state_script() -> str:
    return """
if (typeof render === 'function') render();
return {
  href: location.href,
  search: location.search,
  scene,
  summary: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
  battleCandidate: battleState?.candidate ? {
    id: battleState.candidate.id || '',
    blockId: battleState.candidate.blockId || '',
    battleBackground: battleState.candidate.battleBackground || '',
    enemySpriteOnly: battleState.candidate.enemySpriteOnly === true,
    enemyCns: battleState.candidate.enemyCns || '',
    enemyAssetKey: battleState.candidate.enemyAssetKey || '',
  } : null,
  background: battleState?.background?.name || '',
  enemyName: battleState?.enemy?.name || '',
  enemyProfile: battleState?.enemy?.profile || null,
  enemyVisual: battleState?.enemy?.visual || null,
  battleStartFeedbackLog: window.HWANSE_BATTLE_START_FEEDBACK_LOG || [],
  battleStartFeedbackLast: window.HWANSE_LAST_BATTLE_START_FEEDBACK || null,
  battleStartFeedbackRender: window.HWANSE_BATTLE_START_FEEDBACK_RENDER || [],
  battleStartFeedbackLastRender: window.HWANSE_LAST_BATTLE_START_FEEDBACK_RENDER || null,
};
"""


def start_dialogue_battle_link_script() -> str:
    return """
window.__hwanseDialogueBattleLink = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
window.HWANSE_BATTLE_START_FEEDBACK_LOG = [];
window.HWANSE_BATTLE_START_FEEDBACK_RENDER = [];
window.HWANSE_LAST_BATTLE_START_FEEDBACK = null;
window.HWANSE_LAST_BATTLE_START_FEEDBACK_RENDER = null;
if (typeof activeBattleStartFeedbacks !== 'undefined') activeBattleStartFeedbacks = [];
Promise.all([ensureSceneEvents(), ensureEventDialogueBlocks(), ensureBattleData()])
  .then(() => {
    const block = eventDialogueBlocks.find((row) => row.blockId === 'event-dialogue-block-015');
    if (!block) throw new Error('missing event-dialogue-block-015');
    const started = startDialogue(block);
    if (!started) throw new Error('could not start dialogue block 015');
    let guard = 0;
    while (activeDialogue && guard < 80) {
      advanceDialogue();
      guard += 1;
    }
    const finish = () => {
      const link = window.HWANSE_LAST_DIALOGUE_BATTLE_LINK || null;
      if (!link) {
        window.setTimeout(finish, 50);
        return;
      }
      const objectiveBefore = prototypeObjectiveState();
      let actionTile = null;
      for (let y = 0; y < map.height; y += 1) {
        for (let x = 0; x < map.width; x += 1) {
          setPlayerPosition(x * map.tileSize - 16, y * map.tileSize - 31);
          publishPrototypeCompletionState();
          drawActionPrompt();
          if ((window.HWANSE_LAST_ACTION_PROMPT || {}).kind === 'dialogue-battle-link') {
            actionTile = { x, y };
            y = map.height;
            break;
          }
        }
      }
      if (!actionTile) throw new Error('missing dialogue-battle-link action prompt');
      setPlayerPosition(actionTile.x * map.tileSize - 16, actionTile.y * map.tileSize - 31);
      publishPrototypeCompletionState();
      if (typeof render === 'function') render();
      drawActionPrompt();
      const actionPromptBefore = window.HWANSE_LAST_ACTION_PROMPT || null;
      const actionResult = activateHotspotAtFoot();
      const dialogueBattleLinkAction = window.HWANSE_LAST_DIALOGUE_BATTLE_LINK_ACTION || null;
      const waitBattle = () => {
        if (scene === 'battle' && battleState) {
          if (typeof render === 'function') render();
          let savedPayload = null;
          try {
            savedPayload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || 'null');
          } catch (error) {
            savedPayload = null;
          }
          window.__hwanseDialogueBattleLink = {
            started,
            guard,
            link,
            objectiveBefore,
            actionPromptBefore,
            actionResult,
            dialogueBattleLinkAction,
            actionTile,
            scene,
            map: map?.name || '',
            summary: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
            enemyName: battleState?.enemy?.name || '',
            enemyHp: battleState?.enemy?.hp ?? null,
            enemyHpMax: battleState?.enemy?.hpMax ?? null,
            enemyAtk: battleState?.enemy?.atk ?? null,
            enemyDef: battleState?.enemy?.def ?? null,
            enemyAction: battleState?.enemy?.action || null,
            enemyProfile: battleState?.enemy?.profile || null,
            enemyProfileSource: battleState?.enemy?.profileSource || '',
            enemyVisual: battleState?.enemy?.visual || null,
            enemyOriginalEnemyRowBound: battleState?.enemy?.originalEnemyRowBound ?? null,
            enemyOriginalStatsOrRewardsBound: battleState?.enemy?.originalStatsOrRewardsBound ?? null,
            progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
            progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
            battleCandidate: battleState?.candidate ? {
              id: battleState.candidate.id || '',
              blockId: battleState.candidate.blockId || '',
              battleBackground: battleState.candidate.battleBackground || '',
            } : null,
            battleStartFeedbackLog: window.HWANSE_BATTLE_START_FEEDBACK_LOG || [],
            battleStartFeedbackRender: window.HWANSE_BATTLE_START_FEEDBACK_RENDER || [],
            battleStartFeedbackLast: window.HWANSE_LAST_BATTLE_START_FEEDBACK || null,
            battleStartFeedbackLastRender: window.HWANSE_LAST_BATTLE_START_FEEDBACK_RENDER || null,
            autoSave: window.HWANSE_LAST_DIALOGUE_BATTLE_LINK_AUTO_SAVE || null,
            dialogueCompleteAutoSave: window.HWANSE_LAST_DIALOGUE_COMPLETE_AUTO_SAVE || null,
            savedPayload,
            log: battleState.log || [],
          };
          return;
        }
        window.setTimeout(waitBattle, 50);
      };
      waitBattle();
    };
    finish();
  })
  .catch((error) => {
    window.__hwanseDialogueBattleLink = { error: String(error && error.message || error) };
  });
return true;
"""


def dialogue_battle_link_state_script() -> str:
    return "return window.__hwanseDialogueBattleLink || null;"


def start_battle_completion_script() -> str:
    return """
window.__hwanseCandidateBattleCompletion = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
window.HWANSE_BATTLE_REWARD_EFFECT_LOG = [];
window.HWANSE_BATTLE_REWARD_EFFECT_RENDER = [];
window.HWANSE_LAST_BATTLE_REWARD_EFFECT = null;
window.HWANSE_LAST_BATTLE_REWARD_EFFECT_RENDER = null;
if (typeof activeBattleRewardEffect !== 'undefined') activeBattleRewardEffect = null;
ensureBattleData()
  .then(() => startBattlePrototype())
  .then(() => {
    const state = ensureBattleRuntimeState();
    for (const character of state.characters || []) {
      character.exp = 96;
      character.expMax = 100;
    }
    syncBattlePartyFromRuntimeState();
    battleState.enemy.hp = 1;
    useSelectedBattleCommand();
    if (!battleState?.finished || !battleState?.rewardGranted) {
      throw new Error('candidate battle did not finish after forced low HP attack');
    }
    if (typeof render === 'function') render();
    const victorySummary = window.HWANSE_LAST_BATTLE_PROTOTYPE || null;
    const victoryAutoSave = window.HWANSE_LAST_BATTLE_VICTORY_SAVE || null;
    const rewardEffect = window.HWANSE_LAST_BATTLE_REWARD_EFFECT || null;
    const rewardEffectRender = window.HWANSE_LAST_BATTLE_REWARD_EFFECT_RENDER || null;
    const rewardEffectLog = window.HWANSE_BATTLE_REWARD_EFFECT_LOG || [];
    useSelectedBattleCommand();
    updateBattleButton();
    const raw = localStorage.getItem(RUNTIME_SAVE_KEY) || '{}';
    const payload = JSON.parse(raw);
    const button = document.getElementById('battleButton');
    window.__hwanseCandidateBattleCompletion = {
      saved: victoryAutoSave?.saved === true,
      victoryAutoSave,
      savedPayload: payload,
      scene,
      map: map?.name || '',
      buttonText: button?.textContent || '',
      buttonTitle: button?.title || '',
      completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      progressReviewBlock: typeof prototypeProgressReviewBlock === 'function'
        ? prototypeProgressReviewBlock()
        : null,
      progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
      savedProgress: payload.prototypeProgress || null,
      runtimeState: runtimeState ? {
        money: runtimeState.money,
        items: (runtimeState.items || []).map((item) => ({
          key: item.key,
          name: item.name,
          count: item.count || 0,
        })),
        characterCount: (runtimeState.characters || []).length,
        expTotal: (runtimeState.characters || []).reduce((total, character) => total + (character.exp || 0), 0),
        characterExp: (runtimeState.characters || []).map((character) => ({
          name: character.name,
          level: character.level || 0,
          exp: character.exp || 0,
          expMax: character.expMax || 0,
          hpMax: character.hpMax || 0,
          mpMax: character.mpMax || 0,
          atk: (character.baseStats || {}).atk || 0,
          def: (character.baseStats || {}).def || 0,
        })),
      } : null,
      loadedSaveSummary: loadedSaveSummary || null,
      victorySummary,
      rewardEffect,
      rewardEffectRender,
      rewardEffectLog,
    };
  })
  .catch((error) => {
    window.__hwanseCandidateBattleCompletion = { error: String(error && error.message || error) };
  });
return true;
"""


def capture_battle_completion_after_title_continue_script() -> str:
    return """
window.__hwanseCandidateBattleCompletionTitleRestore = null;
Promise.all([ensureSceneEvents(), ensureSavePointCandidates(), ensureEventDialogueBlocks(), ensureBattleData()])
  .then(() => {
    updateBattleButton();
    let tileX = 20;
    let tileY = 20;
    for (let y = 0; y < map.height; y += 1) {
      for (let x = 0; x < map.width; x += 1) {
        setPlayerPosition(x * map.tileSize - 16, y * map.tileSize - 31);
        publishPrototypeCompletionState();
        drawActionPrompt();
        if ((window.HWANSE_LAST_ACTION_PROMPT || {}).kind === 'battle-candidate') {
          tileX = x;
          tileY = y;
          y = map.height;
          break;
        }
      }
    }
    setPlayerPosition(tileX * map.tileSize - 16, tileY * map.tileSize - 31);
    publishPrototypeCompletionState();
    drawActionPrompt();
    const actionPrompt = window.HWANSE_LAST_ACTION_PROMPT || null;
    const objectiveBefore = typeof prototypeObjectiveState === 'function'
      ? prototypeObjectiveState()
      : null;
    const objectiveResult = typeof activatePrototypeObjectiveAction === 'function'
      ? activatePrototypeObjectiveAction()
      : false;
    const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
    const objectiveCompletionNotice = window.HWANSE_LAST_BATTLE_COMPLETION_NOTICE || null;
    const objectiveActiveDialogueBlock = activeDialogue ? {
      blockId: activeDialogue.block?.blockId || '',
      line: activeDialogue.lines?.[activeDialogue.index] || '',
      lineCount: activeDialogue.lines?.length || 0,
    } : null;
    activeDialogue = null;
    const completedActionResult = activateHotspotAtFoot();
    const completionNotice = window.HWANSE_LAST_BATTLE_COMPLETION_NOTICE || null;
    const activeDialogueBlock = activeDialogue ? {
      blockId: activeDialogue.block?.blockId || '',
      line: activeDialogue.lines?.[activeDialogue.index] || '',
      lineCount: activeDialogue.lines?.length || 0,
    } : null;
    const button = document.getElementById('battleButton');
    const menuCommands = commandMenuItems().map((item) => ({
      key: item.key,
      name: item.name,
      command: item.command,
      usable: item.usable,
    }));
    activeDialogue = null;
    const menuEntries = menuItems();
    const menuBattleIndex = menuEntries.findIndex((item) => item.command === 'startBattlePrototype');
    const menuBattleName = menuEntries[menuBattleIndex]?.name || '';
    selectedMenuItemIndex = menuBattleIndex;
    const menuBattleResult = useSelectedMenuItem();
    window.setTimeout(() => {
      if (typeof render === 'function') render();
      const menuCompletionNotice = window.HWANSE_LAST_BATTLE_COMPLETION_NOTICE || null;
      const menuActiveDialogueBlock = activeDialogue ? {
        blockId: activeDialogue.block?.blockId || '',
        line: activeDialogue.lines?.[activeDialogue.index] || '',
        lineCount: activeDialogue.lines?.length || 0,
      } : null;
      window.__hwanseCandidateBattleCompletionTitleRestore = {
        loaded: true,
        titleContinue: true,
        scene,
        map: map?.name || '',
        search: location.search,
        buttonText: button?.textContent || '',
        buttonTitle: button?.title || '',
        actionPrompt,
        objectiveBefore,
        objectiveResult,
        objectiveAction,
        objectiveCompletionNotice,
        objectiveActiveDialogueBlock,
        completedActionResult,
        completionNotice,
        activeDialogueBlock,
        menuBattleName,
        menuBattleResult,
        menuCompletionNotice,
        menuActiveDialogueBlock,
        battleCompletionFeedbackLog: window.HWANSE_BATTLE_COMPLETION_FEEDBACK_LOG || [],
        battleCompletionFeedbackRender: window.HWANSE_BATTLE_COMPLETION_FEEDBACK_RENDER || [],
        battleCompletionFeedbackLast: window.HWANSE_LAST_BATTLE_COMPLETION_FEEDBACK || null,
        battleCompletionFeedbackLastRender: window.HWANSE_LAST_BATTLE_COMPLETION_FEEDBACK_RENDER || null,
        completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
        progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
        progressReviewBlock: typeof prototypeProgressReviewBlock === 'function'
          ? prototypeProgressReviewBlock()
          : null,
        menuCommands,
        loadedSaveSummary: loadedSaveSummary || null,
        quickLoadHidden: document.getElementById('quickLoadButton')?.hidden ?? null,
        quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
        runtimeState: runtimeState ? {
          money: runtimeState.money,
          items: (runtimeState.items || []).map((item) => ({
            key: item.key,
            name: item.name,
            count: item.count || 0,
          })),
          expTotal: (runtimeState.characters || []).reduce((total, character) => total + (character.exp || 0), 0),
          characterExp: (runtimeState.characters || []).map((character) => ({
            name: character.name,
            level: character.level || 0,
            exp: character.exp || 0,
            expMax: character.expMax || 0,
          })),
        } : null,
      };
    }, 0);
  })
  .catch((error) => {
    window.__hwanseCandidateBattleCompletionTitleRestore = { error: String(error && error.message || error) };
  });
return true;
"""


def restore_battle_completion_script() -> str:
    return """
window.__hwanseCandidateBattleCompletionRestore = null;
Promise.all([ensureSceneEvents(), ensureSavePointCandidates(), ensureEventDialogueBlocks(), ensureBattleData()])
  .then(() => quickLoadRuntime())
  .then((loaded) => Promise.all([ensureSceneEvents(), ensureSavePointCandidates(), ensureEventDialogueBlocks(), ensureBattleData()]).then(() => {
    updateBattleButton();
    let tileX = 20;
    let tileY = 20;
    for (let y = 0; y < map.height; y += 1) {
      for (let x = 0; x < map.width; x += 1) {
        setPlayerPosition(x * map.tileSize - 16, y * map.tileSize - 31);
        publishPrototypeCompletionState();
        drawActionPrompt();
        if ((window.HWANSE_LAST_ACTION_PROMPT || {}).kind === 'battle-candidate') {
          tileX = x;
          tileY = y;
          y = map.height;
          break;
        }
      }
    }
    setPlayerPosition(tileX * map.tileSize - 16, tileY * map.tileSize - 31);
    publishPrototypeCompletionState();
    drawActionPrompt();
    const actionPrompt = window.HWANSE_LAST_ACTION_PROMPT || null;
    const objectiveBefore = typeof prototypeObjectiveState === 'function'
      ? prototypeObjectiveState()
      : null;
    const objectiveResult = typeof activatePrototypeObjectiveAction === 'function'
      ? activatePrototypeObjectiveAction()
      : false;
    const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
    const objectiveCompletionNotice = window.HWANSE_LAST_BATTLE_COMPLETION_NOTICE || null;
    const objectiveActiveDialogueBlock = activeDialogue ? {
      blockId: activeDialogue.block?.blockId || '',
      line: activeDialogue.lines?.[activeDialogue.index] || '',
      lineCount: activeDialogue.lines?.length || 0,
    } : null;
    activeDialogue = null;
    const completedActionResult = activateHotspotAtFoot();
    const completionNotice = window.HWANSE_LAST_BATTLE_COMPLETION_NOTICE || null;
    const activeDialogueBlock = activeDialogue ? {
      blockId: activeDialogue.block?.blockId || '',
      line: activeDialogue.lines?.[activeDialogue.index] || '',
      lineCount: activeDialogue.lines?.length || 0,
    } : null;
    const button = document.getElementById('battleButton');
    const menuCommands = commandMenuItems().map((item) => ({
      key: item.key,
      name: item.name,
      command: item.command,
      usable: item.usable,
    }));
    activeDialogue = null;
    const menuEntries = menuItems();
    const menuBattleIndex = menuEntries.findIndex((item) => item.command === 'startBattlePrototype');
    const menuBattleName = menuEntries[menuBattleIndex]?.name || '';
    selectedMenuItemIndex = menuBattleIndex;
    const menuBattleResult = useSelectedMenuItem();
    window.setTimeout(() => {
      if (typeof render === 'function') render();
      const menuCompletionNotice = window.HWANSE_LAST_BATTLE_COMPLETION_NOTICE || null;
      const menuActiveDialogueBlock = activeDialogue ? {
        blockId: activeDialogue.block?.blockId || '',
        line: activeDialogue.lines?.[activeDialogue.index] || '',
        lineCount: activeDialogue.lines?.length || 0,
      } : null;
      window.__hwanseCandidateBattleCompletionRestore = {
        loaded,
        scene,
        map: map?.name || '',
        buttonText: button?.textContent || '',
        buttonTitle: button?.title || '',
        actionPrompt,
        objectiveBefore,
        objectiveResult,
        objectiveAction,
        objectiveCompletionNotice,
        objectiveActiveDialogueBlock,
        completedActionResult,
        completionNotice,
        activeDialogueBlock,
        menuBattleName,
        menuBattleResult,
        menuCompletionNotice,
        menuActiveDialogueBlock,
        battleCompletionFeedbackLog: window.HWANSE_BATTLE_COMPLETION_FEEDBACK_LOG || [],
        battleCompletionFeedbackRender: window.HWANSE_BATTLE_COMPLETION_FEEDBACK_RENDER || [],
        battleCompletionFeedbackLast: window.HWANSE_LAST_BATTLE_COMPLETION_FEEDBACK || null,
        battleCompletionFeedbackLastRender: window.HWANSE_LAST_BATTLE_COMPLETION_FEEDBACK_RENDER || null,
        completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
        progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
        progressReviewBlock: typeof prototypeProgressReviewBlock === 'function'
          ? prototypeProgressReviewBlock()
          : null,
        menuCommands,
        loadedSaveSummary: loadedSaveSummary || null,
      };
    }, 0);
  }))
  .catch((error) => {
    window.__hwanseCandidateBattleCompletionRestore = { error: String(error && error.message || error) };
  });
return true;
"""


def start_battle_run_script() -> str:
    return """
window.__hwanseCandidateBattleRun = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
window.HWANSE_BATTLE_OUTCOME_FEEDBACK_LOG = [];
window.HWANSE_BATTLE_OUTCOME_FEEDBACK_RENDER = [];
window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK = null;
window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK_RENDER = null;
window.HWANSE_LAST_BATTLE_RUN_FEEDBACK = null;
if (typeof activeBattleOutcomeFeedbacks !== 'undefined') activeBattleOutcomeFeedbacks = [];
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
ensureBattleData()
  .then(() => startBattlePrototype())
  .then(() => {
    const commands = battleCommandItems(currentBattleActor());
    const runIndex = commands.findIndex((command) => command.key === 'run');
    const runCommand = runIndex >= 0 ? commands[runIndex] : null;
    battleState.selectedCommandIndex = runIndex >= 0 ? runIndex : Math.max(0, commands.length - 1);
    const result = useSelectedBattleCommand();
    if (typeof render === 'function') render();
    updateBattleButton();
    const outcomeFeedbackLog = [...(window.HWANSE_BATTLE_OUTCOME_FEEDBACK_LOG || [])];
    const outcomeFeedbackRender = [...(window.HWANSE_BATTLE_OUTCOME_FEEDBACK_RENDER || [])];
    const outcomeFeedbackLast = window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK || null;
    const outcomeFeedbackLastRender = window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK_RENDER || null;
    const runFeedback = window.HWANSE_LAST_BATTLE_RUN_FEEDBACK || null;
    const noticeBeforeObjective = menuNotice;
    const objectiveBefore = prototypeObjectiveState();
    const objectiveResult = activatePrototypeObjectiveAction();
    const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
    const objectiveCompletionNotice = window.HWANSE_LAST_BATTLE_OUTCOME_COMPLETION_NOTICE || null;
    const objectiveActiveDialogueBlock = activeDialogue?.block || null;
    if (typeof render === 'function') render();
    const outcomeNoticeFeedbackLog = [...(window.HWANSE_BATTLE_OUTCOME_FEEDBACK_LOG || [])];
    const outcomeNoticeFeedbackRender = [...(window.HWANSE_BATTLE_OUTCOME_FEEDBACK_RENDER || [])];
    const outcomeNoticeFeedbackLast = window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK || null;
    const outcomeNoticeFeedbackLastRender = window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK_RENDER || null;
    const savedText = localStorage.getItem(RUNTIME_SAVE_KEY) || '';
    let savedPayload = null;
    try {
      savedPayload = savedText ? JSON.parse(savedText) : null;
    } catch (error) {
      savedPayload = { error: String(error && error.message || error) };
    }
    window.__hwanseCandidateBattleRun = {
      result,
      scene,
      map: map?.name || '',
      notice: noticeBeforeObjective,
      menuNoticeAfterObjective: menuNotice,
      battleStateActive: !!battleState,
      runCommand: runCommand ? {
        key: runCommand.key || '',
        name: runCommand.name || '',
        nameSource: runCommand.nameSource || '',
        textTableKey: runCommand.textTableKey || '',
        textTableIndex: runCommand.textTableIndex ?? null,
        textTableRefVaHex: runCommand.textTableRefVaHex || '',
        textTableTextVaHex: runCommand.textTableTextVaHex || '',
      } : null,
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
      completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
      objectiveBefore,
      objectiveResult,
      objectiveAction,
      objectiveCompletionNotice,
      objectiveActiveDialogueBlock,
      runtimeState: runtimeState ? {
        money: runtimeState.money || 0,
        expTotal: (runtimeState.characters || []).reduce((total, character) => total + (character.exp || 0), 0),
      } : null,
      outcomeFeedbackLog,
      outcomeFeedbackRender,
      outcomeFeedbackLast,
      outcomeFeedbackLastRender,
      outcomeNoticeFeedbackLog,
      outcomeNoticeFeedbackRender,
      outcomeNoticeFeedbackLast,
      outcomeNoticeFeedbackLastRender,
      runFeedback,
      summary: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
      autoSave: window.HWANSE_LAST_BATTLE_RUN_AUTO_SAVE || null,
      savedPayload,
    };
  })
  .catch((error) => {
    window.__hwanseCandidateBattleRun = { error: String(error && error.message || error) };
  });
return true;
"""


def battle_run_state_script() -> str:
    return "return window.__hwanseCandidateBattleRun || null;"


def start_battle_defeat_script() -> str:
    return """
window.__hwanseCandidateBattleDefeat = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
window.HWANSE_BATTLE_OUTCOME_FEEDBACK_LOG = [];
window.HWANSE_BATTLE_OUTCOME_FEEDBACK_RENDER = [];
window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK = null;
window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK_RENDER = null;
window.HWANSE_BATTLE_DEFEND_FEEDBACK_LOG = [];
window.HWANSE_BATTLE_DEFEND_FEEDBACK_RENDER = [];
window.HWANSE_LAST_BATTLE_DEFEND_FEEDBACK = null;
window.HWANSE_LAST_BATTLE_DEFEND_FEEDBACK_RENDER = null;
window.HWANSE_LAST_BATTLE_DEFEAT_FEEDBACK = null;
if (typeof activeBattleOutcomeFeedbacks !== 'undefined') activeBattleOutcomeFeedbacks = [];
if (typeof activeBattleDefendFeedbacks !== 'undefined') activeBattleDefendFeedbacks = [];
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
ensureBattleData()
  .then(() => startBattlePrototype())
  .then(() => {
    const commands = battleCommandItems(currentBattleActor());
    const defendIndex = commands.findIndex((command) => command.key === 'defend');
    const defendCommand = defendIndex >= 0 ? commands[defendIndex] : null;
    battleState.selectedCommandIndex = defendIndex >= 0 ? defendIndex : 0;
    for (const member of battleState.party || []) {
      member.hp = 0;
      syncBattleMember(member);
    }
    if (!battleState.party.length) throw new Error('candidate battle party is empty');
    battleState.party[0].hp = 1;
    battleState.party[0].hpMax = Math.max(1, battleState.party[0].hpMax || 1);
    syncBattleMember(battleState.party[0]);
    battleState.activeActorIndex = 0;
    const result = useSelectedBattleCommand();
    if (typeof render === 'function') render();
    const beforeClose = {
      scene,
      finished: battleState?.finished ?? null,
      finishMessage: battleState?.finishMessage || '',
      rewardGranted: battleState?.rewardGranted ?? null,
      enemyHp: battleState?.enemy?.hp ?? null,
      partyHp: (battleState?.party || []).map((member) => member.hp),
      log: battleState?.log || [],
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
      turnMarker: window.HWANSE_LAST_BATTLE_TURN_RESULT || null,
      turnAutoSave: window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE || window.HWANSE_LAST_BATTLE_TURN_RESULT?.autoSave || null,
      defendCommand: defendCommand ? {
        key: defendCommand.key || '',
        name: defendCommand.name || '',
        nameSource: defendCommand.nameSource || '',
        textTableKey: defendCommand.textTableKey || '',
        textTableIndex: defendCommand.textTableIndex ?? null,
        textTableRefVaHex: defendCommand.textTableRefVaHex || '',
        textTableTextVaHex: defendCommand.textTableTextVaHex || '',
      } : null,
      defendFeedbackLog: window.HWANSE_BATTLE_DEFEND_FEEDBACK_LOG || [],
      defendFeedbackRender: window.HWANSE_BATTLE_DEFEND_FEEDBACK_RENDER || [],
      defendFeedbackLast: window.HWANSE_LAST_BATTLE_DEFEND_FEEDBACK || null,
      defendFeedbackLastRender: window.HWANSE_LAST_BATTLE_DEFEND_FEEDBACK_RENDER || null,
      completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
      runtimeState: runtimeState ? {
        money: runtimeState.money || 0,
        expTotal: (runtimeState.characters || []).reduce((total, character) => total + (character.exp || 0), 0),
        partyHp: (runtimeState.characters || []).map((character) => ({
          name: character.name,
          hp: character.hp || 0,
          hpMax: character.hpMax || 0,
        })),
      } : null,
      summary: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
    };
    const closeResult = useSelectedBattleCommand();
    if (typeof render === 'function') render();
    updateBattleButton();
    const outcomeFeedbackLog = [...(window.HWANSE_BATTLE_OUTCOME_FEEDBACK_LOG || [])];
    const outcomeFeedbackRender = [...(window.HWANSE_BATTLE_OUTCOME_FEEDBACK_RENDER || [])];
    const outcomeFeedbackLast = window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK || null;
    const outcomeFeedbackLastRender = window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK_RENDER || null;
    const defeatFeedback = window.HWANSE_LAST_BATTLE_DEFEAT_FEEDBACK || null;
    const noticeBeforeObjective = menuNotice;
    const objectiveBefore = prototypeObjectiveState();
    const objectiveResult = activatePrototypeObjectiveAction();
    const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
    const objectiveCompletionNotice = window.HWANSE_LAST_BATTLE_OUTCOME_COMPLETION_NOTICE || null;
    const objectiveActiveDialogueBlock = activeDialogue?.block || null;
    if (typeof render === 'function') render();
    const outcomeNoticeFeedbackLog = [...(window.HWANSE_BATTLE_OUTCOME_FEEDBACK_LOG || [])];
    const outcomeNoticeFeedbackRender = [...(window.HWANSE_BATTLE_OUTCOME_FEEDBACK_RENDER || [])];
    const outcomeNoticeFeedbackLast = window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK || null;
    const outcomeNoticeFeedbackLastRender = window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK_RENDER || null;
    const savedText = localStorage.getItem(RUNTIME_SAVE_KEY) || '';
    let savedPayload = null;
    try {
      savedPayload = savedText ? JSON.parse(savedText) : null;
    } catch (error) {
      savedPayload = { error: String(error && error.message || error) };
    }
    window.__hwanseCandidateBattleDefeat = {
      result,
      closeResult,
      beforeClose,
      scene,
      map: map?.name || '',
      notice: noticeBeforeObjective,
      menuNoticeAfterObjective: menuNotice,
      battleStateActive: !!battleState,
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
      completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
      objectiveBefore,
      objectiveResult,
      objectiveAction,
      objectiveCompletionNotice,
      objectiveActiveDialogueBlock,
      defeatRecovery: window.HWANSE_LAST_BATTLE_DEFEAT_RECOVERY || null,
      runtimeState: runtimeState ? {
        money: runtimeState.money || 0,
        expTotal: (runtimeState.characters || []).reduce((total, character) => total + (character.exp || 0), 0),
        partyHp: (runtimeState.characters || []).map((character) => ({
          name: character.name,
          hp: character.hp || 0,
          hpMax: character.hpMax || 0,
        })),
      } : null,
      outcomeFeedbackLog,
      outcomeFeedbackRender,
      outcomeFeedbackLast,
      outcomeFeedbackLastRender,
      outcomeNoticeFeedbackLog,
      outcomeNoticeFeedbackRender,
      outcomeNoticeFeedbackLast,
      outcomeNoticeFeedbackLastRender,
      defeatFeedback,
      autoSave: window.HWANSE_LAST_BATTLE_DEFEAT_AUTO_SAVE || null,
      savedPayload,
    };
  })
  .catch((error) => {
    window.__hwanseCandidateBattleDefeat = { error: String(error && error.message || error) };
  });
return true;
"""


def battle_defeat_state_script() -> str:
    return "return window.__hwanseCandidateBattleDefeat || null;"


def capture_battle_nonvictory_after_title_continue_script(kind: str) -> str:
    return f"""
window.__hwanseCandidateBattleNonVictoryTitleRestore = null;
Promise.all([ensureSceneEvents(), ensureSavePointCandidates(), ensureEventDialogueBlocks(), ensureBattleData()])
  .then(() => {{
    updateBattleButton();
    refreshPlayHudLines();
    const completion = publishPrototypeCompletionState();
    const objectiveBefore = prototypeObjectiveState();
    window.HWANSE_BATTLE_OUTCOME_FEEDBACK_LOG = [];
    window.HWANSE_BATTLE_OUTCOME_FEEDBACK_RENDER = [];
    window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK = null;
    window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK_RENDER = null;
    if (typeof activeBattleOutcomeFeedbacks !== 'undefined') activeBattleOutcomeFeedbacks = [];
    const objectiveResult = activatePrototypeObjectiveAction();
    const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
    const objectiveCompletionNotice = window.HWANSE_LAST_BATTLE_OUTCOME_COMPLETION_NOTICE || null;
    const objectiveActiveDialogueBlock = activeDialogue?.block || null;
    if (typeof render === 'function') render();
    const outcomeNoticeFeedbackLog = window.HWANSE_BATTLE_OUTCOME_FEEDBACK_LOG || [];
    const outcomeNoticeFeedbackRender = window.HWANSE_BATTLE_OUTCOME_FEEDBACK_RENDER || [];
    const outcomeNoticeFeedbackLast = window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK || null;
    const outcomeNoticeFeedbackLastRender = window.HWANSE_LAST_BATTLE_OUTCOME_FEEDBACK_RENDER || null;
    window.__hwanseCandidateBattleNonVictoryTitleRestore = {{
      titleContinue: true,
      expectedKind: {json.dumps(kind)},
      scene,
      map: map?.name || '',
      search: location.search,
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      completion,
      objectiveBefore,
      objectiveResult,
      objectiveAction,
      objectiveCompletionNotice,
      objectiveActiveDialogueBlock,
      outcomeNoticeFeedbackLog,
      outcomeNoticeFeedbackRender,
      outcomeNoticeFeedbackLast,
      outcomeNoticeFeedbackLastRender,
      buttonText: document.getElementById('battleButton')?.textContent || '',
      quickLoadHidden: document.getElementById('quickLoadButton')?.hidden ?? null,
      quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
      playHud: refreshPlayHudLines(),
      runtimeState: runtimeState ? {{
        money: runtimeState.money || 0,
        expTotal: (runtimeState.characters || []).reduce((total, character) => total + (character.exp || 0), 0),
        partyHp: (runtimeState.characters || []).map((character) => ({{
          name: character.name,
          hp: character.hp || 0,
          hpMax: character.hpMax || 0,
        }})),
      }} : null,
      fieldEncounter: fieldEncounterSavePayload(),
      loadedSaveSummary: loadedSaveSummary || null,
    }};
  }})
  .catch((error) => {{
    window.__hwanseCandidateBattleNonVictoryTitleRestore = {{ error: String(error && error.message || error) }};
  }});
return true;
"""


def battle_nonvictory_title_restore_state_script() -> str:
    return "return window.__hwanseCandidateBattleNonVictoryTitleRestore || null;"


def start_battle_status_cure_script() -> str:
    return """
window.__hwanseCandidateBattleStatusCure = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
window.HWANSE_BATTLE_ITEM_FEEDBACK_LOG = [];
window.HWANSE_BATTLE_ITEM_FEEDBACK_RENDER = [];
window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK = null;
window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK_RENDER = null;
if (typeof activeBattleItemFeedbacks !== 'undefined') activeBattleItemFeedbacks = [];
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
ensureBattleData()
  .then(() => startBattlePrototype())
	  .then(() => {
		    const state = ensureBattleRuntimeState();
		    const herb = ensureBattleRuntimeInventoryItem(state, 'herb');
		    herb.name = battlePrototypeItemName('herb', '약초');
		    herb.count = 1;
		    const antidote = ensureBattleRuntimeInventoryItem(state, 'item_2');
		    antidote.name = battlePrototypeItemName('item_2', '해독초');
		    antidote.count = 1;
		    syncBattlePartyFromRuntimeState();
		    function restoreFirstActorForFailureSetup() {
		      const actor = (battleState.party || [])[0] || null;
		      if (!actor) return null;
		      actor.hp = actor.hpMax || actor.hp || 0;
		      actor.statuses = [];
		      if (actor.source) {
		        actor.source.hp = actor.hp;
		        actor.source.statuses = [];
		      }
		      syncBattleMember(actor);
		      syncLoadedSaveRuntimeInventory();
		      battleState.activeActorIndex = 0;
		      battleState.itemMenuOpen = false;
		      battleState.selectedCommandIndex = 0;
		      battleState.selectedItemIndex = 0;
		      if (battleState.enemy) battleState.enemy.statusInflicted = {};
		      return actor;
		    }
	    function resetBattleItemFeedbackState() {
	      window.HWANSE_BATTLE_ITEM_FEEDBACK_LOG = [];
	      window.HWANSE_BATTLE_ITEM_FEEDBACK_RENDER = [];
	      window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK = null;
	      window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK_RENDER = null;
	      if (typeof activeBattleItemFeedbacks !== 'undefined') activeBattleItemFeedbacks = [];
	    }
	    function resetBattleItemUseState() {
	      window.HWANSE_LAST_BATTLE_ITEM_USE = null;
	      window.HWANSE_LAST_BATTLE_ITEM_USE_AUTO_SAVE = null;
	      window.HWANSE_LAST_BATTLE_ITEM_FAILURE = null;
	    }
	    function resetSoundState() {
	      window.HWANSE_SOUND_COUNTS = {};
	      window.HWANSE_SOUND_LOG = [];
	      window.HWANSE_LAST_SOUND = null;
	    }
	    function soundStateSnapshot() {
	      return {
	        counts: { ...(window.HWANSE_SOUND_COUNTS || {}) },
	        last: window.HWANSE_LAST_SOUND || null,
	        log: Array.isArray(window.HWANSE_SOUND_LOG) ? [...window.HWANSE_SOUND_LOG] : [],
	      };
	    }
	    function selectBattleInventoryItem(itemKey) {
	      const commands = battleCommandItems(currentBattleActor());
	      const itemIndex = commands.findIndex((command) => command.key === 'item');
	      battleState.selectedCommandIndex = itemIndex >= 0 ? itemIndex : 0;
	      const itemCommand = commands[battleState.selectedCommandIndex] || null;
	      const choicesBefore = battleInventoryItems();
	      const openedOrUsed = useSelectedBattleCommand();
	      const itemChoices = battleInventoryItems();
	      const choicePool = battleState.itemMenuOpen ? itemChoices : choicesBefore;
	      const itemChoiceIndex = choicePool.findIndex((item) => item.key === itemKey);
	      if (battleState.itemMenuOpen) {
	        battleState.selectedItemIndex = itemChoiceIndex >= 0 ? itemChoiceIndex : 0;
	      }
	      const itemChoice = choicePool[battleState.selectedItemIndex || 0] || choicePool[0] || null;
	      const itemResult = battleState.itemMenuOpen ? useSelectedBattleCommand() : openedOrUsed;
	      return {
	        itemResult,
	        itemCommand,
	        itemChoice,
	        itemChoices: choicePool.map((item) => ({ key: item.key, name: item.name, count: item.count })),
	      };
	    }
		    function runNoTargetFailure() {
		      const actor = restoreFirstActorForFailureSetup() || currentBattleActor();
		      const herbBefore = (runtimeState.items || []).find((item) => item.key === 'herb') || null;
		      resetBattleItemFeedbackState();
		      resetBattleItemUseState();
		      resetSoundState();
		      const before = actor ? {
		        name: actor.name,
		        hp: actor.hp || 0,
		        hpMax: actor.hpMax || 0,
		        statuses: [...(actor.statuses || [])],
		        sourceStatuses: [...(actor.source?.statuses || [])],
		      } : null;
		      const selection = selectBattleInventoryItem('herb');
		      if (typeof render === 'function') render();
		      const afterMember = before
		        ? (battleState.party || []).find((member) => member.name === before.name)
		        : null;
		      const herbAfter = (runtimeState.items || []).find((item) => item.key === 'herb') || null;
		      return {
		        scene,
		        map: map?.name || '',
		        before,
		        after: afterMember ? {
		          name: afterMember.name,
		          hp: afterMember.hp || 0,
		          hpMax: afterMember.hpMax || 0,
		          statuses: [...(afterMember.statuses || [])],
		          sourceStatuses: [...(afterMember.source?.statuses || [])],
		        } : null,
		        itemResult: selection.itemResult,
		        itemCommand: selection.itemCommand ? {
		          key: selection.itemChoice?.key || selection.itemCommand.key,
		          name: selection.itemChoice?.name || selection.itemCommand.name,
		        } : null,
		        itemChoices: selection.itemChoices,
		        failure: window.HWANSE_LAST_BATTLE_ITEM_FAILURE || null,
		        itemUse: window.HWANSE_LAST_BATTLE_ITEM_USE || null,
		        itemUseAutoSave: window.HWANSE_LAST_BATTLE_ITEM_USE_AUTO_SAVE || null,
		        itemFeedbackLog: window.HWANSE_BATTLE_ITEM_FEEDBACK_LOG || [],
		        itemFeedbackRender: window.HWANSE_BATTLE_ITEM_FEEDBACK_RENDER || [],
		        itemFeedbackLast: window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK || null,
		        itemFeedbackLastRender: window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK_RENDER || null,
		        itemCountBefore: herbBefore ? herbBefore.count : null,
		        itemCountAfter: herbAfter ? herbAfter.count : null,
		        progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
		        turnResult: window.HWANSE_LAST_BATTLE_TURN_RESULT || null,
		        turnAutoSave: window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE || null,
		        soundState: soundStateSnapshot(),
		        log: battleState.log || [],
		      };
		    }
		    function runNoStatusFailure() {
		      const actor = currentBattleActor();
	      const antidoteBefore = (runtimeState.items || []).find((item) => item.key === 'item_2') || null;
	      resetBattleItemFeedbackState();
	      resetBattleItemUseState();
	      resetSoundState();
	      const before = actor ? {
	        name: actor.name,
	        hp: actor.hp || 0,
	        statuses: [...(actor.statuses || [])],
	        sourceStatuses: [...(actor.source?.statuses || [])],
	      } : null;
	      const selection = selectBattleInventoryItem('item_2');
	      if (typeof render === 'function') render();
	      const afterMember = before
	        ? (battleState.party || []).find((member) => member.name === before.name)
	        : null;
	      const antidoteAfter = (runtimeState.items || []).find((item) => item.key === 'item_2') || null;
	      return {
	        scene,
	        map: map?.name || '',
	        before,
	        after: afterMember ? {
	          name: afterMember.name,
	          hp: afterMember.hp || 0,
	          statuses: [...(afterMember.statuses || [])],
	          sourceStatuses: [...(afterMember.source?.statuses || [])],
	        } : null,
	        itemResult: selection.itemResult,
	        itemCommand: selection.itemCommand ? {
	          key: selection.itemChoice?.key || selection.itemCommand.key,
	          name: selection.itemChoice?.name || selection.itemCommand.name,
	        } : null,
	        itemChoices: selection.itemChoices,
	        failure: window.HWANSE_LAST_BATTLE_ITEM_FAILURE || null,
	        itemUse: window.HWANSE_LAST_BATTLE_ITEM_USE || null,
	        itemUseAutoSave: window.HWANSE_LAST_BATTLE_ITEM_USE_AUTO_SAVE || null,
	        itemFeedbackLog: window.HWANSE_BATTLE_ITEM_FEEDBACK_LOG || [],
	        itemFeedbackRender: window.HWANSE_BATTLE_ITEM_FEEDBACK_RENDER || [],
	        itemFeedbackLast: window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK || null,
	        itemFeedbackLastRender: window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK_RENDER || null,
	        itemCountBefore: antidoteBefore ? antidoteBefore.count : null,
	        itemCountAfter: antidoteAfter ? antidoteAfter.count : null,
	        progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
	        turnResult: window.HWANSE_LAST_BATTLE_TURN_RESULT || null,
	        turnAutoSave: window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE || null,
	        soundState: soundStateSnapshot(),
	        log: battleState.log || [],
	      };
	    }
		    const noTargetFailure = runNoTargetFailure();
		    restoreFirstActorForFailureSetup();
		    const noStatusFailure = runNoStatusFailure();
	    resetBattleItemFeedbackState();
	    resetBattleItemUseState();
	    resetSoundState();
	    const summary = window.HWANSE_LAST_BATTLE_PROTOTYPE || null;
	    const actorBefore = currentBattleActor();
	    const attackResult = useSelectedBattleCommand();
    const poisonedMember = (battleState.party || []).find((member) => (member.statuses || []).includes('poison')) || null;
    const afterPoison = poisonedMember ? {
      name: poisonedMember.name,
      statuses: [...(poisonedMember.statuses || [])],
      sourceStatuses: [...(poisonedMember.source?.statuses || [])],
      hp: poisonedMember.hp || 0,
    } : null;
	    const itemSelection = selectBattleInventoryItem('item_2');
	    const itemCommand = itemSelection.itemCommand || null;
	    const itemChoice = itemSelection.itemChoice || null;
	    const itemResult = itemSelection.itemResult;
    if (typeof render === 'function') render();
    const curedMember = poisonedMember
      ? (battleState.party || []).find((member) => member.name === poisonedMember.name)
      : null;
    const runtimeAntidote = (runtimeState.items || []).find((item) => item.key === 'item_2') || null;
    window.__hwanseCandidateBattleStatusCure = {
      scene,
      map: map?.name || '',
      summary,
      actorBefore: actorBefore?.name || '',
      attackResult,
      afterPoison,
      itemResult,
      itemCommand: itemCommand ? {
        key: itemChoice?.key || itemCommand.key,
        name: itemChoice?.name || itemCommand.name,
      } : null,
      afterCure: curedMember ? {
        name: curedMember.name,
        statuses: [...(curedMember.statuses || [])],
        sourceStatuses: [...(curedMember.source?.statuses || [])],
        hp: curedMember.hp || 0,
      } : null,
      itemUse: window.HWANSE_LAST_BATTLE_ITEM_USE || null,
      itemFeedbackLog: window.HWANSE_BATTLE_ITEM_FEEDBACK_LOG || [],
      itemFeedbackRender: window.HWANSE_BATTLE_ITEM_FEEDBACK_RENDER || [],
	      itemFeedbackLast: window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK || null,
	      itemFeedbackLastRender: window.HWANSE_LAST_BATTLE_ITEM_FEEDBACK_RENDER || null,
	      noTargetFailure,
	      noStatusFailure,
	      itemCountAfter: runtimeAntidote ? runtimeAntidote.count : null,
      enemyStatusInflicted: battleState.enemy?.statusInflicted || null,
      enemyAction: battleState.enemy?.action || null,
      log: battleState.log || [],
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
      originalStatusFormulaImplemented: false,
      originalEnemyAiImplemented: false,
    };
  })
  .catch((error) => {
    window.__hwanseCandidateBattleStatusCure = { error: String(error && error.message || error) };
  });
return true;
"""


def battle_status_cure_state_script() -> str:
    return "return window.__hwanseCandidateBattleStatusCure || null;"


def start_battle_status_persist_script() -> str:
    return """
window.__hwanseCandidateBattleStatusPersist = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_LOG = [];
window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER = [];
window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK = null;
window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER = null;
window.HWANSE_LAST_BATTLE_ENEMY_ATTACK_ANIMATION = null;
window.HWANSE_LAST_BATTLE_ENEMY_ATTACK_ANIMATION_RENDER = null;
window.HWANSE_BATTLE_ENEMY_ATTACK_ANIMATION_LOG = [];
if (typeof activeBattleStatusFeedbacks !== 'undefined') activeBattleStatusFeedbacks = [];
if (typeof activeBattleEnemyAttackAnimation !== 'undefined') activeBattleEnemyAttackAnimation = null;
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
ensureBattleData()
  .then(() => startBattlePrototype())
  .then(() => {
    const summary = window.HWANSE_LAST_BATTLE_PROTOTYPE || null;
    const actorBefore = currentBattleActor();
    const attackResult = useSelectedBattleCommand();
    if (typeof render === 'function') render();
    const poisonedMember = (battleState.party || []).find((member) => (member.statuses || []).includes('poison')) || null;
    const autoSave = window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE || window.HWANSE_LAST_BATTLE_TURN_RESULT?.autoSave || null;
    let savedPayload = null;
    try {
      savedPayload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
    } catch (error) {
      savedPayload = { error: String(error && error.message || error) };
    }
    const savedCharacter = (savedPayload?.runtimeState?.characters || []).find((member) => member.name === actorBefore?.name) || null;
    window.__hwanseCandidateBattleStatusPersist = {
      scene,
      map: map?.name || '',
      summary,
      actorBefore: actorBefore?.name || '',
      attackResult,
      afterPoison: poisonedMember ? {
        name: poisonedMember.name,
        statuses: [...(poisonedMember.statuses || [])],
        sourceStatuses: [...(poisonedMember.source?.statuses || [])],
        hp: poisonedMember.hp || 0,
      } : null,
      turnResult: window.HWANSE_LAST_BATTLE_TURN_RESULT || null,
      statusFeedbackLog: window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_LOG || [],
      statusFeedbackRender: window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER || [],
      statusFeedbackLast: window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK || null,
      statusFeedbackLastRender: window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER || null,
      autoSave,
      savedPayload,
      savedCharacter: savedCharacter ? {
        name: savedCharacter.name,
        statuses: [...(savedCharacter.statuses || [])],
        hp: savedCharacter.hp || 0,
      } : null,
      enemyStatusInflicted: battleState.enemy?.statusInflicted || null,
      enemyAction: battleState.enemy?.action || null,
      log: battleState.log || [],
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
      originalStatusFormulaImplemented: false,
      originalEnemyAiImplemented: false,
    };
  })
  .catch((error) => {
    window.__hwanseCandidateBattleStatusPersist = { error: String(error && error.message || error) };
  });
return true;
"""


def battle_status_persist_state_script() -> str:
    return "return window.__hwanseCandidateBattleStatusPersist || null;"


def start_battle_status_effect_turn_script() -> str:
    return """
window.__hwanseCandidateBattleStatusEffectTurn = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_LOG = [];
window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER = [];
window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK = null;
window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER = null;
if (typeof activeBattleStatusFeedbacks !== 'undefined') activeBattleStatusFeedbacks = [];
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
ensureBattleData()
  .then(() => startBattlePrototype())
  .then(() => {
    const summary = window.HWANSE_LAST_BATTLE_PROTOTYPE || null;
    const firstActor = currentBattleActor();
    const firstAttackResult = useSelectedBattleCommand();
    if (typeof render === 'function') render();
    const firstEnemyAttackAnimation = window.HWANSE_LAST_BATTLE_ENEMY_ATTACK_ANIMATION || null;
    const firstEnemyAttackAnimationRender = window.HWANSE_LAST_BATTLE_ENEMY_ATTACK_ANIMATION_RENDER || null;
    const poisonedMember = (battleState.party || []).find((member) => (member.statuses || []).includes('poison')) || null;
    const afterFirst = poisonedMember ? {
      name: poisonedMember.name,
      hp: poisonedMember.hp || 0,
      statuses: [...(poisonedMember.statuses || [])],
      sourceStatuses: [...(poisonedMember.source?.statuses || [])],
    } : null;
    const firstTurnResult = window.HWANSE_LAST_BATTLE_TURN_RESULT || null;
    const secondActor = currentBattleActor();
    const hpBeforeSecondCommand = secondActor?.hp || 0;
    const secondAttackResult = useSelectedBattleCommand();
    if (typeof render === 'function') render();
    const secondEnemyAttackAnimation = window.HWANSE_LAST_BATTLE_ENEMY_ATTACK_ANIMATION || null;
    const secondEnemyAttackAnimationRender = window.HWANSE_LAST_BATTLE_ENEMY_ATTACK_ANIMATION_RENDER || null;
    const statusEffect = window.HWANSE_LAST_BATTLE_STATUS_EFFECT || null;
    const secondTurnResult = window.HWANSE_LAST_BATTLE_TURN_RESULT || null;
    const autoSave = window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE || secondTurnResult?.autoSave || null;
    let savedPayload = null;
    try {
      savedPayload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
    } catch (error) {
      savedPayload = { error: String(error && error.message || error) };
    }
    const savedCharacter = (savedPayload?.runtimeState?.characters || []).find((member) => member.name === firstActor?.name) || null;
    window.__hwanseCandidateBattleStatusEffectTurn = {
      scene,
      map: map?.name || '',
      summary,
      firstActor: firstActor?.name || '',
      firstAttackResult,
      firstEnemyAttackAnimation,
      firstEnemyAttackAnimationRender,
      firstTurnResult,
      afterFirst,
      secondActor: secondActor?.name || '',
      hpBeforeSecondCommand,
      secondAttackResult,
      secondEnemyAttackAnimation,
      secondEnemyAttackAnimationRender,
      statusEffect,
      statusFeedbackLog: window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_LOG || [],
      statusFeedbackRender: window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER || [],
      statusFeedbackLast: window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK || null,
      statusFeedbackLastRender: window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER || null,
      secondTurnResult,
      autoSave,
      savedPayload,
      savedCharacter: savedCharacter ? {
        name: savedCharacter.name,
        statuses: [...(savedCharacter.statuses || [])],
        hp: savedCharacter.hp || 0,
      } : null,
      enemyStatusInflicted: battleState.enemy?.statusInflicted || null,
      enemyAction: battleState.enemy?.action || null,
      log: battleState.log || [],
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
      originalStatusFormulaImplemented: false,
      originalEnemyAiImplemented: false,
      originalCombatFormulaImplemented: false,
    };
  })
  .catch((error) => {
    window.__hwanseCandidateBattleStatusEffectTurn = { error: String(error && error.message || error) };
  });
return true;
"""


def battle_status_effect_turn_state_script() -> str:
    return "return window.__hwanseCandidateBattleStatusEffectTurn || null;"


def start_battle_paralysis_effect_turn_script() -> str:
    return """
window.__hwanseCandidateBattleParalysisTurn = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_LOG = [];
window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER = [];
window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK = null;
window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER = null;
if (typeof activeBattleStatusFeedbacks !== 'undefined') activeBattleStatusFeedbacks = [];
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
ensureBattleData()
  .then(() => startBattlePrototype())
  .then(() => {
    const summary = window.HWANSE_LAST_BATTLE_PROTOTYPE || null;
    const firstActor = currentBattleActor();
    const firstAttackResult = useSelectedBattleCommand();
    const paralyzedMember = (battleState.party || []).find((member) => (member.statuses || []).includes('paralysis')) || null;
    const afterFirst = paralyzedMember ? {
      name: paralyzedMember.name,
      hp: paralyzedMember.hp || 0,
      statuses: [...(paralyzedMember.statuses || [])],
      sourceStatuses: [...(paralyzedMember.source?.statuses || [])],
    } : null;
    const firstTurnResult = window.HWANSE_LAST_BATTLE_TURN_RESULT || null;
    const enemyHpAfterFirst = battleState.enemy?.hp || 0;
    const secondActor = currentBattleActor();
    const hpBeforeSecondCommand = secondActor?.hp || 0;
    const enemyHpBeforeSecondCommand = battleState.enemy?.hp || 0;
    const secondCommandName = (battleCommandItems(secondActor)[battleState.selectedCommandIndex] || {}).name || '';
    const secondCommandResult = useSelectedBattleCommand();
    if (typeof render === 'function') render();
    const statusEffect = window.HWANSE_LAST_BATTLE_STATUS_EFFECT || null;
    const secondTurnResult = window.HWANSE_LAST_BATTLE_TURN_RESULT || null;
    const autoSave = window.HWANSE_LAST_BATTLE_TURN_AUTO_SAVE || secondTurnResult?.autoSave || null;
    let savedPayload = null;
    try {
      savedPayload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
    } catch (error) {
      savedPayload = { error: String(error && error.message || error) };
    }
    const savedCharacter = (savedPayload?.runtimeState?.characters || []).find((member) => member.name === firstActor?.name) || null;
    window.__hwanseCandidateBattleParalysisTurn = {
      scene,
      map: map?.name || '',
      summary,
      firstActor: firstActor?.name || '',
      firstAttackResult,
      firstTurnResult,
      afterFirst,
      enemyHpAfterFirst,
      secondActor: secondActor?.name || '',
      hpBeforeSecondCommand,
      enemyHpBeforeSecondCommand,
      secondCommandName,
      secondCommandResult,
      enemyHpAfterSecondCommand: battleState.enemy?.hp || 0,
      statusEffect,
      statusFeedbackLog: window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_LOG || [],
      statusFeedbackRender: window.HWANSE_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER || [],
      statusFeedbackLast: window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK || null,
      statusFeedbackLastRender: window.HWANSE_LAST_BATTLE_STATUS_EFFECT_FEEDBACK_RENDER || null,
      secondTurnResult,
      autoSave,
      savedPayload,
      savedCharacter: savedCharacter ? {
        name: savedCharacter.name,
        statuses: [...(savedCharacter.statuses || [])],
        hp: savedCharacter.hp || 0,
      } : null,
      enemyStatusInflicted: battleState.enemy?.statusInflicted || null,
      enemyAction: battleState.enemy?.action || null,
      log: battleState.log || [],
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      completion: window.HWANSE_LAST_PROTOTYPE_COMPLETION || null,
      originalStatusFormulaImplemented: false,
      originalEnemyAiImplemented: false,
      originalCombatFormulaImplemented: false,
    };
  })
  .catch((error) => {
    window.__hwanseCandidateBattleParalysisTurn = { error: String(error && error.message || error) };
  });
return true;
"""


def battle_paralysis_effect_turn_state_script() -> str:
    return "return window.__hwanseCandidateBattleParalysisTurn || null;"


def capture_battle_status_persist_after_title_continue_script() -> str:
    return """
window.__hwanseCandidateBattleStatusPersistTitleRestore = null;
Promise.all([ensureSceneEvents(), ensureSavePointCandidates(), ensureEventDialogueBlocks(), ensureBattleData()])
  .then(() => {
    updateBattleButton();
    refreshPlayHudLines();
    const status = partyStatusMenuState();
    const rows = status.rows || [];
    const poisoned = rows.find((row) => (row.statuses || []).includes('poison')) || null;
    let savedPayload = null;
    try {
      savedPayload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
    } catch (error) {
      savedPayload = { error: String(error && error.message || error) };
    }
    const objectiveBefore = prototypeObjectiveState();
    window.HWANSE_BATTLE_COMPLETION_FEEDBACK_LOG = [];
    window.HWANSE_BATTLE_COMPLETION_FEEDBACK_RENDER = [];
    window.HWANSE_LAST_BATTLE_COMPLETION_FEEDBACK = null;
    window.HWANSE_LAST_BATTLE_COMPLETION_FEEDBACK_RENDER = null;
    if (typeof activeBattleCompletionFeedbacks !== 'undefined') activeBattleCompletionFeedbacks = [];
    const objectiveResult = activatePrototypeObjectiveAction();
    const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
    const objectiveCompletionNotice = window.HWANSE_LAST_BATTLE_TURN_COMPLETION_NOTICE || null;
    const objectiveActiveDialogueBlock = activeDialogue?.block || null;
    if (typeof render === 'function') render();
    const battleTurnCompletionFeedbackLog = window.HWANSE_BATTLE_COMPLETION_FEEDBACK_LOG || [];
    const battleTurnCompletionFeedbackRender = window.HWANSE_BATTLE_COMPLETION_FEEDBACK_RENDER || [];
    const battleTurnCompletionFeedbackLast = window.HWANSE_LAST_BATTLE_COMPLETION_FEEDBACK || null;
    const battleTurnCompletionFeedbackLastRender = window.HWANSE_LAST_BATTLE_COMPLETION_FEEDBACK_RENDER || null;
    window.__hwanseCandidateBattleStatusPersistTitleRestore = {
      titleContinue: true,
      scene,
      map: map?.name || '',
      search: window.location.search,
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      completion: publishPrototypeCompletionState(),
      statusRows: rows,
      statusReview: statusMenuReviewBlock(status),
      poisoned,
      objectiveBefore,
      objectiveResult,
      objectiveAction,
      objectiveCompletionNotice,
      objectiveActiveDialogueBlock,
      battleTurnCompletionFeedbackLog,
      battleTurnCompletionFeedbackRender,
      battleTurnCompletionFeedbackLast,
      battleTurnCompletionFeedbackLastRender,
      savedPayload,
      runtimeState: runtimeState ? {
        money: runtimeState.money,
        characters: (runtimeState.characters || []).map((member) => ({
          name: member.name,
          hp: member.hp,
          hpMax: member.hpMax,
          statuses: [...(member.statuses || [])],
        })),
      } : null,
      buttonText: document.getElementById('battleButton')?.textContent || '',
      quickLoadHidden: document.getElementById('quickLoadButton')?.hidden ?? null,
      quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
      playHud: window.HWANSE_LAST_PLAY_HUD_LINES || [],
      loadedSaveSummary,
      originalStatusFormulaImplemented: false,
      originalEnemyAiImplemented: false,
    };
  })
  .catch((error) => {
    window.__hwanseCandidateBattleStatusPersistTitleRestore = { error: String(error && error.message || error) };
  });
return true;
"""


def battle_status_persist_title_restore_state_script() -> str:
    return "return window.__hwanseCandidateBattleStatusPersistTitleRestore || null;"


def start_field_encounter_script() -> str:
    return """
window.__hwanseFieldEncounter = null;
window.HWANSE_FIELD_ENCOUNTER_FEEDBACK_LOG = [];
window.HWANSE_FIELD_ENCOUNTER_FEEDBACK_RENDER = [];
window.HWANSE_LAST_FIELD_ENCOUNTER_FEEDBACK = null;
window.HWANSE_LAST_FIELD_ENCOUNTER_FEEDBACK_RENDER = null;
if (typeof activeFieldEncounterFeedbacks !== 'undefined') activeFieldEncounterFeedbacks = [];
window.HWANSE_BATTLE_START_FEEDBACK_LOG = [];
window.HWANSE_BATTLE_START_FEEDBACK_RENDER = [];
window.HWANSE_LAST_BATTLE_START_FEEDBACK = null;
window.HWANSE_LAST_BATTLE_START_FEEDBACK_RENDER = null;
if (typeof activeBattleStartFeedbacks !== 'undefined') activeBattleStartFeedbacks = [];
localStorage.removeItem(RUNTIME_SAVE_KEY);
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
ensureBattleData()
  .then(() => {
    fieldEncounterState = createFieldEncounterState({
      enabled: true,
      stepCount: 0,
      lastMap: map?.name || '',
    });
    syncMapQuery();
    updateBattleButton();
    const before = {
      menuLabel: fieldEncounterMenuLabel(),
      savePayload: fieldEncounterSavePayload(),
      urlSearch: window.location.search,
      foot: footTile(),
    };
    const stepInputs = [
      { code: 'ArrowRight', key: 'ArrowRight', dx: 1, dy: 0 },
      { code: 'ArrowLeft', key: 'ArrowLeft', dx: -1, dy: 0 },
      { code: 'ArrowRight', key: 'ArrowRight', dx: 1, dy: 0 },
      { code: 'ArrowLeft', key: 'ArrowLeft', dx: -1, dy: 0 },
      { code: 'ArrowRight', key: 'ArrowRight', dx: 1, dy: 0 },
      { code: 'ArrowLeft', key: 'ArrowLeft', dx: -1, dy: 0 },
    ];
    const movementSteps = [];
    const fail = (message, extra = {}) => {
      window.__hwanseFieldEncounter = {
        error: message,
        before,
        movementInput: true,
        movementSteps,
        ...extra,
      };
    };
    const finish = (started) => {
        if (started && typeof render === 'function') render();
        const startSnapshot = {
          scene,
          summary: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
          progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
          battleStartFeedbackLog: window.HWANSE_BATTLE_START_FEEDBACK_LOG || [],
          battleStartFeedbackRender: window.HWANSE_BATTLE_START_FEEDBACK_RENDER || [],
          battleStartFeedbackLast: window.HWANSE_LAST_BATTLE_START_FEEDBACK || null,
          battleStartFeedbackLastRender: window.HWANSE_LAST_BATTLE_START_FEEDBACK_RENDER || null,
          musicCue: window.HWANSE_LAST_MUSIC_CUE || null,
          musicCueLog: window.HWANSE_MUSIC_CUE_LOG || [],
          musicSynth: window.HWANSE_LAST_MUSIC_SYNTH || null,
          battleCandidate: battleState?.candidate ? {
            id: battleState.candidate.id || '',
            blockId: battleState.candidate.blockId || '',
            battleBackground: battleState.candidate.battleBackground || '',
          } : null,
        };
        const encounterFeedbackLog = window.HWANSE_FIELD_ENCOUNTER_FEEDBACK_LOG || [];
        const encounterFeedbackRender = window.HWANSE_FIELD_ENCOUNTER_FEEDBACK_RENDER || [];
        const encounterFeedbackLast = window.HWANSE_LAST_FIELD_ENCOUNTER_FEEDBACK || null;
        const encounterFeedbackLastRender = window.HWANSE_LAST_FIELD_ENCOUNTER_FEEDBACK_RENDER || 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();
        }
        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;
        }
        const completion = publishPrototypeCompletionState();
        const objectiveBefore = typeof prototypeObjectiveState === 'function'
          ? prototypeObjectiveState()
          : null;
        const objectiveResult = typeof activatePrototypeObjectiveAction === 'function'
          ? activatePrototypeObjectiveAction()
          : false;
        const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
        const objectiveCompletionNotice = window.HWANSE_LAST_FIELD_ENCOUNTER_COMPLETION_NOTICE || null;
        const objectiveActiveDialogueBlock = activeDialogue ? {
          blockId: activeDialogue.block?.blockId || '',
          line: activeDialogue.lines?.[activeDialogue.index] || '',
          lineCount: activeDialogue.lines?.length || 0,
        } : null;
        if (typeof render === 'function') render();
        const objectiveEncounterFeedbackLog = window.HWANSE_FIELD_ENCOUNTER_FEEDBACK_LOG || [];
        const objectiveEncounterFeedbackRender = window.HWANSE_FIELD_ENCOUNTER_FEEDBACK_RENDER || [];
        const objectiveEncounterFeedbackLast = window.HWANSE_LAST_FIELD_ENCOUNTER_FEEDBACK || null;
        const objectiveEncounterFeedbackLastRender = window.HWANSE_LAST_FIELD_ENCOUNTER_FEEDBACK_RENDER || null;
        window.__hwanseFieldEncounter = {
          started,
          before,
          startSnapshot,
          victoryResult,
          closeResult,
          victorySummary,
          victoryAutoSave,
          savedPayload,
          scene,
          map: map?.name || '',
          movementInput: true,
          movementSteps,
          marker: window.HWANSE_LAST_FIELD_ENCOUNTER || null,
          stepMarker: window.HWANSE_LAST_FIELD_ENCOUNTER_STEP || null,
          encounterFeedbackLog,
          encounterFeedbackRender,
          encounterFeedbackLast,
          encounterFeedbackLastRender,
          summary: window.HWANSE_LAST_BATTLE_PROTOTYPE || null,
          progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
          progressRecord: window.HWANSE_LAST_PROGRESS_RECORD || null,
          completion,
          objectiveBefore,
          objectiveResult,
          objectiveAction,
          objectiveCompletionNotice,
          objectiveActiveDialogueBlock,
          objectiveEncounterFeedbackLog,
          objectiveEncounterFeedbackRender,
          objectiveEncounterFeedbackLast,
          objectiveEncounterFeedbackLastRender,
          buttonText: document.getElementById('battleButton')?.textContent || '',
          runtimeState: runtimeState ? {
            money: runtimeState.money || 0,
            items: (runtimeState.items || []).map((item) => ({
              key: item.key,
              name: item.name,
              count: item.count || 0,
            })),
          } : null,
          loadedSaveSummary: loadedSaveSummary || null,
          musicCue: window.HWANSE_LAST_MUSIC_CUE || null,
          musicCueLog: window.HWANSE_MUSIC_CUE_LOG || [],
          musicSynth: window.HWANSE_LAST_MUSIC_SYNTH || null,
          musicSynthStop: window.HWANSE_LAST_MUSIC_SYNTH_STOP || null,
          musicSynthStopLog: window.HWANSE_MUSIC_SYNTH_STOP_LOG || [],
          battleEndMusicCue: window.HWANSE_LAST_BATTLE_END_MUSIC_CUE || null,
          state: fieldEncounterSavePayload(),
          menuLabel: fieldEncounterMenuLabel(),
          battleCandidate: battleState?.candidate ? {
            id: battleState.candidate.id || '',
            blockId: battleState.candidate.blockId || '',
            battleBackground: battleState.candidate.battleBackground || '',
          } : null,
        };
    };
    const runStep = (index) => {
      if (index >= stepInputs.length) {
        const waitStartedAt = performance.now();
        const waitForBattle = () => {
          if (scene === 'battle' && battleState) {
            finish(true);
            return;
          }
          if (performance.now() - waitStartedAt > 2500) {
            finish(false);
            return;
          }
          window.setTimeout(waitForBattle, 50);
        };
        waitForBattle();
        return;
      }
      if (scene !== 'map') {
        fail('scene-changed-before-step-sequence-complete', { index, scene });
        return;
      }
      const input = stepInputs[index];
      const beforeStepFoot = footTile();
      const planned = chooseMovementStep(beforeStepFoot, input.dx, input.dy);
      if (!planned) {
        fail('missing-planned-movement-step', { index, input, beforeStepFoot });
        return;
      }
      window.dispatchEvent(new KeyboardEvent('keydown', {
        bubbles: true,
        cancelable: true,
        code: input.code,
        key: input.key,
      }));
      window.setTimeout(() => {
        window.dispatchEvent(new KeyboardEvent('keyup', {
          bubbles: true,
          cancelable: true,
          code: input.code,
          key: input.key,
        }));
      }, 80);
      const stepStartedAt = performance.now();
      const pollStep = () => {
        const afterStepFoot = footTile();
        const tileChanged = beforeStepFoot?.x !== afterStepFoot?.x || beforeStepFoot?.y !== afterStepFoot?.y;
        if (scene === 'battle' && battleState) {
          movementSteps.push({
            index: index + 1,
            code: input.code,
            beforeFoot: beforeStepFoot,
            afterFoot: afterStepFoot,
            tileChanged,
            fieldEncounter: fieldEncounterSavePayload(),
            marker: window.HWANSE_LAST_FIELD_ENCOUNTER || null,
            stepMarker: window.HWANSE_LAST_FIELD_ENCOUNTER_STEP || null,
          });
          finish(true);
          return;
        }
        if (!player.step && tileChanged) {
          movementSteps.push({
            index: index + 1,
            code: input.code,
            beforeFoot: beforeStepFoot,
            afterFoot: afterStepFoot,
            tileChanged,
            fieldEncounter: fieldEncounterSavePayload(),
            stepMarker: window.HWANSE_LAST_FIELD_ENCOUNTER_STEP || null,
            stepAutoSave: window.HWANSE_LAST_FIELD_ENCOUNTER_STEP_AUTO_SAVE || null,
          });
          window.setTimeout(() => runStep(index + 1), 80);
          return;
        }
        if (performance.now() - stepStartedAt > 2500) {
          fail('movement-step-timeout', {
            index,
            input,
            beforeStepFoot,
            afterStepFoot,
            tileChanged,
            fieldEncounter: fieldEncounterSavePayload(),
          });
          return;
        }
        window.setTimeout(pollStep, 50);
      };
      window.setTimeout(pollStep, 120);
    };
    window.__hwanseFieldEncounter = {
      pending: true,
      movementInput: true,
      before,
      movementSteps,
    };
    runStep(0);
  })
  .catch((error) => {
    window.__hwanseFieldEncounter = { error: String(error && error.message || error) };
  });
return true;
"""


def field_encounter_state_script() -> str:
    return "return window.__hwanseFieldEncounter || null;"


def capture_field_encounter_after_title_continue_script() -> str:
    return """
window.__hwanseFieldEncounterTitleRestore = null;
Promise.all([ensureSceneEvents(), ensureSavePointCandidates(), ensureEventDialogueBlocks(), ensureBattleData()])
  .then(() => {
    updateBattleButton();
    refreshPlayHudLines();
    if (typeof activeDialogue !== 'undefined') activeDialogue = null;
    const completion = publishPrototypeCompletionState();
    const objectiveBefore = typeof prototypeObjectiveState === 'function'
      ? prototypeObjectiveState()
      : null;
    const objectiveResult = typeof activatePrototypeObjectiveAction === 'function'
      ? activatePrototypeObjectiveAction()
      : false;
    const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
    const objectiveCompletionNotice = window.HWANSE_LAST_FIELD_ENCOUNTER_COMPLETION_NOTICE || null;
    const objectiveActiveDialogueBlock = activeDialogue ? {
      blockId: activeDialogue.block?.blockId || '',
      line: activeDialogue.lines?.[activeDialogue.index] || '',
      lineCount: activeDialogue.lines?.length || 0,
    } : null;
    if (typeof render === 'function') render();
    const objectiveEncounterFeedbackLog = window.HWANSE_FIELD_ENCOUNTER_FEEDBACK_LOG || [];
    const objectiveEncounterFeedbackRender = window.HWANSE_FIELD_ENCOUNTER_FEEDBACK_RENDER || [];
    const objectiveEncounterFeedbackLast = window.HWANSE_LAST_FIELD_ENCOUNTER_FEEDBACK || null;
    const objectiveEncounterFeedbackLastRender = window.HWANSE_LAST_FIELD_ENCOUNTER_FEEDBACK_RENDER || null;
    window.__hwanseFieldEncounterTitleRestore = {
      titleContinue: true,
      scene,
      map: map?.name || '',
      search: location.search,
      fieldEncounter: fieldEncounterSavePayload(),
      progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
      completion,
      objectiveBefore,
      objectiveResult,
      objectiveAction,
      objectiveCompletionNotice,
      objectiveActiveDialogueBlock,
      objectiveEncounterFeedbackLog,
      objectiveEncounterFeedbackRender,
      objectiveEncounterFeedbackLast,
      objectiveEncounterFeedbackLastRender,
      buttonText: document.getElementById('battleButton')?.textContent || '',
      quickLoadHidden: document.getElementById('quickLoadButton')?.hidden ?? null,
      quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
      playHud: refreshPlayHudLines(),
      runtimeState: runtimeState ? {
        money: runtimeState.money || 0,
        items: (runtimeState.items || []).map((item) => ({
          key: item.key,
          name: item.name,
          count: item.count || 0,
        })),
      } : null,
      loadedSaveSummary: loadedSaveSummary || null,
    };
  })
  .catch((error) => {
    window.__hwanseFieldEncounterTitleRestore = { error: String(error && error.message || error) };
  });
return true;
"""


def field_encounter_title_restore_state_script() -> str:
    return "return window.__hwanseFieldEncounterTitleRestore || null;"


def battle_completion_state_script() -> str:
    return "return window.__hwanseCandidateBattleCompletion || null;"


def battle_completion_restore_state_script() -> str:
    return "return window.__hwanseCandidateBattleCompletionRestore || null;"


def battle_completion_title_restore_state_script() -> str:
    return "return window.__hwanseCandidateBattleCompletionTitleRestore || null;"


def wait_for_button_state(port: int, session_id: str, timeout: float = 6) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, battle_button_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        if state and state.get("error") is None:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"battle candidate button did not become ready: {state!r}")


def wait_for_battle_completion_state(port: int, session_id: str, timeout: float = 8) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, battle_completion_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        battle = ((state.get("completion") or {}).get("battle") or {})
        if state and state.get("error") is None and battle.get("completed") is True:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle completion did not become ready: {state!r}")


def wait_for_battle_action_marker(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, battle_action_marker_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        if state and state.get("error") is None:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle action marker did not become ready: {state!r}")


def wait_for_battle_item_target_selection_state(port: int, session_id: str, timeout: float = 10) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, battle_item_target_selection_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        item_use = state.get("itemUse") or {}
        if (
            state
            and state.get("error") is None
            and state.get("ok") is True
            and item_use.get("targetName") == "Rinshan"
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"battle item target selection state did not become ready: {state!r}")


def wait_for_battle_skill_action_state(port: int, session_id: str, timeout: float = 10) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, battle_skill_action_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        skill_use = state.get("skillUse") or {}
        animation = state.get("partyActionAnimation") or {}
        if (
            state
            and state.get("error") is None
            and state.get("ok") is True
            and skill_use.get("source") == "prototype-battle-skill-effect"
            and animation.get("commandKind") == "skill"
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"battle skill action state did not become ready: {state!r}")


def wait_for_dialogue_battle_link_state(port: int, session_id: str, timeout: float = 10) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, dialogue_battle_link_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("scene") == "battle"
            and ((state.get("summary") or {}).get("dialogueBattleLink") is True)
            and counts.get("dialogue-battle-link") == 1
            and counts.get("battle-start") == 1
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"dialogue battle link state did not become ready: {state!r}")


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


def wait_for_battle_completion_title_ready(port: int, session_id: str, timeout: float = 10) -> 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 {}
        labels = [str(label) for label in (state.get("titleMenuLabels") or [])]
        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("bootError") is None
            and state.get("quickLoadHidden") is False
            and state.get("quickLoadText") == "이어하기"
            and "continue" in (state.get("titleMenuKeys") or [])
            and any("이어하기 map1_02b 11,12" in label for label in labels)
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle title continue did not become ready: {state!r}")


def wait_for_title_continue_ready_for_map(
    port: int,
    session_id: str,
    map_name: str,
    tile_label: str,
    timeout: float = 10,
) -> dict:
    deadline = time.monotonic() + timeout
    expected_label = f"이어하기 {map_name} {tile_label}"
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, title_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        labels = [str(label) for label in (state.get("titleMenuLabels") or [])]
        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("bootError") is None
            and state.get("quickLoadHidden") is False
            and state.get("quickLoadText") == "이어하기"
            and "continue" in (state.get("titleMenuKeys") or [])
            and any(expected_label in label for label in labels)
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"title continue for {expected_label!r} did not become ready: {state!r}")


def wait_for_battle_completion_title_restore_state(port: int, session_id: str, timeout: float = 10) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, battle_completion_title_restore_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        battle = ((state.get("completion") or {}).get("battle") or {})
        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") == "map1_02b"
            and battle.get("completed") is True
            and counts.get("battle-victory") == 1
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle title continue restore did not become ready: {state!r}")


def wait_for_battle_run_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, battle_run_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 counts.get("battle-run") == 1:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle run state did not become ready: {state!r}")


def wait_for_battle_defeat_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, battle_defeat_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 counts.get("battle-defeat") == 1:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle defeat state did not become ready: {state!r}")


def wait_for_battle_nonvictory_title_restore_state(
    port: int,
    session_id: str,
    kind: str,
    timeout: float = 10,
) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, battle_nonvictory_title_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") == "map1_02b"
            and counts.get(kind) == 1
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle {kind} title continue restore did not become ready: {state!r}")


def wait_for_battle_status_cure_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, battle_status_cure_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        if state and state.get("error") is None and state.get("itemResult") is True:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle status cure state did not become ready: {state!r}")


def wait_for_battle_status_persist_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, battle_status_persist_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        auto_save = state.get("autoSave") or {}
        if (
            state
            and state.get("error") is None
            and state.get("attackResult") is True
            and auto_save.get("saved") is True
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle status persist state did not become ready: {state!r}")


def wait_for_battle_status_effect_turn_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, battle_status_effect_turn_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        auto_save = state.get("autoSave") or {}
        counts = ((state.get("progress") or {}).get("counts") or {})
        if (
            state
            and state.get("error") is None
            and state.get("secondAttackResult") is True
            and auto_save.get("saved") is True
            and counts.get("battle-status-effect-prototype") == 1
            and counts.get("battle-turn-prototype") == 2
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle status effect turn state did not become ready: {state!r}")


def wait_for_battle_paralysis_effect_turn_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, battle_paralysis_effect_turn_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        auto_save = state.get("autoSave") or {}
        counts = ((state.get("progress") or {}).get("counts") or {})
        if (
            state
            and state.get("error") is None
            and state.get("secondCommandResult") is True
            and auto_save.get("saved") is True
            and counts.get("battle-status-effect-prototype") == 1
            and counts.get("battle-turn-prototype") == 2
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle paralysis effect turn state did not become ready: {state!r}")


def wait_for_battle_status_persist_title_restore_state(port: int, session_id: str, timeout: float = 10) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, battle_status_persist_title_restore_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        progress = state.get("progress") or {}
        counts = progress.get("counts") or {}
        poisoned = state.get("poisoned") or {}
        if (
            state
            and state.get("error") is None
            and state.get("titleContinue") is True
            and state.get("map") == "map2_07e"
            and "poison" in (poisoned.get("statuses") or [])
            and counts.get("battle-turn-prototype") == 1
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"candidate battle status persist title restore did not become ready: {state!r}")


def wait_for_field_encounter_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, field_encounter_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("started") is True and counts.get("field-encounter") == 1:
            return state
        time.sleep(0.2)
    raise WebDriverError(f"field encounter state did not become ready: {state!r}")


def wait_for_field_encounter_title_restore_state(port: int, session_id: str, timeout: float = 10) -> dict:
    deadline = time.monotonic() + timeout
    state: dict = {}
    while time.monotonic() < deadline:
        value = execute_js(port, session_id, field_encounter_title_restore_state_script(), timeout=3)
        state = value if isinstance(value, dict) else {}
        counts = (state.get("progress") or {}).get("counts") or {}
        field = state.get("fieldEncounter") or {}
        runtime = state.get("runtimeState") or {}
        if (
            state
            and state.get("error") is None
            and state.get("titleContinue") is True
            and state.get("map") == "map1_02b"
            and field.get("enabled") is True
            and counts.get("field-encounter-victory") == 1
            and runtime.get("money") == 75
        ):
            return state
        time.sleep(0.2)
    raise WebDriverError(f"field encounter title continue restore did not become ready: {state!r}")


def verify_enemy_visual_state(state: dict, expected: dict[str, str]) -> None:
    expected_enemy = expected.get("enemySpriteCandidate")
    if not expected_enemy:
        return
    summary = state.get("summary") or {}
    visual = state.get("enemyVisual") or {}
    profile = state.get("enemyProfile") or {}
    action = state.get("enemyAction") or {}
    action_text = expected_battle_action_text_table(expected.get("enemyActionIndex"))
    verify_battle_vm_replay(summary.get("vmReplay") or {}, expected["blockId"])
    if (
        summary.get("enemySpriteCandidate") != expected_enemy
        or summary.get("enemySpriteAssetKey") != expected.get("enemySpriteAssetKey")
        or summary.get("enemySpriteRefVaHex") != expected.get("enemySpriteRefVaHex")
        or summary.get("battleBackgroundRefVaHex") != expected.get("battleBackgroundRefVaHex")
        or summary.get("enemyName") != expected.get("enemyName")
        or summary.get("enemyHp") != expected.get("enemyHp")
        or summary.get("enemyAtk") != expected.get("enemyAtk")
        or summary.get("enemyDef") != expected.get("enemyDef")
        or summary.get("enemyActionName") != expected.get("enemyActionName")
        or summary.get("enemyActionIndex") != expected.get("enemyActionIndex")
        or summary.get("enemyActionSource") != "prototype-enemy-action"
        or summary.get("enemyActionNameSource") != "exe-text-table-battleCommands"
        or summary.get("enemyActionTextTableKey") != action_text["key"]
        or summary.get("enemyActionTextTableIndex") != action_text["index"]
        or summary.get("enemyActionTextTableRefVaHex") != action_text["refVaHex"]
        or summary.get("enemyActionTextTableTextVaHex") != action_text["textVaHex"]
        or summary.get("enemyRewardExp") != expected.get("enemyRewardExp")
        or summary.get("enemyDropKey") != expected.get("enemyDropKey")
        or summary.get("enemyDropName") != expected.get("enemyDropName")
        or summary.get("enemyDropCount") != expected.get("enemyDropCount")
        or not item_text_provenance_matches(summary, expected.get("enemyDropKey"), prefix="enemyDropItem")
        or summary.get("enemyProfileSource") != "prototype-enemy-profile"
        or summary.get("battleEnemySpriteSelectionKind") != "nearest-exe-resource-descriptor"
        or summary.get("originalEnemyRowBound") is not False
        or summary.get("originalStatsOrRewardsBound") is not False
        or summary.get("originalRewardTableMapped") is not False
        or summary.get("originalDropTableMapped") is not False
        or summary.get("originalEnemyAiImplemented") is not False
        or summary.get("originalStatusFormulaImplemented") is not False
        or summary.get("originalCombatFormulaImplemented") is not False
        or state.get("enemyName") != expected.get("enemyName")
        or state.get("enemyHpMax") != expected.get("enemyHp")
        or state.get("enemyAtk") != expected.get("enemyAtk")
        or state.get("enemyDef") != expected.get("enemyDef")
        or state.get("enemyProfileSource") != "prototype-enemy-profile"
        or profile.get("name") != expected.get("enemyName")
        or profile.get("hp") != expected.get("enemyHp")
        or profile.get("atk") != expected.get("enemyAtk")
        or profile.get("def") != expected.get("enemyDef")
        or profile.get("actionName") != expected.get("enemyActionName")
        or profile.get("actionIndex") != expected.get("enemyActionIndex")
        or profile.get("actionSource") != "prototype-enemy-action"
        or profile.get("actionNameSource") != "exe-text-table-battleCommands"
        or profile.get("actionTextTableKey") != action_text["key"]
        or profile.get("actionTextTableIndex") != action_text["index"]
        or profile.get("actionTextTableRefVaHex") != action_text["refVaHex"]
        or profile.get("actionTextTableTextVaHex") != action_text["textVaHex"]
        or profile.get("dropKey") != expected.get("enemyDropKey")
        or profile.get("dropName") != expected.get("enemyDropName")
        or profile.get("dropCount") != expected.get("enemyDropCount")
        or not item_text_provenance_matches(profile.get("dropItemTextTable") or {}, expected.get("enemyDropKey"))
        or profile.get("source") != "prototype-enemy-profile"
        or profile.get("originalRewardTableMapped") is not False
        or profile.get("originalDropTableMapped") is not False
        or profile.get("originalEnemyAiImplemented") is not False
        or profile.get("originalStatusFormulaImplemented") is not False
        or action.get("name") != expected.get("enemyActionName")
        or action.get("index") != expected.get("enemyActionIndex")
        or action.get("source") != "prototype-enemy-action"
        or action.get("nameSource") != "exe-text-table-battleCommands"
        or action.get("textTableKey") != action_text["key"]
        or action.get("textTableIndex") != action_text["index"]
        or action.get("textTableRefVaHex") != action_text["refVaHex"]
        or action.get("textTableTextVaHex") != action_text["textVaHex"]
        or action.get("originalEnemyAiImplemented") is not False
        or action.get("originalStatusFormulaImplemented") is not False
        or visual.get("enemyCns") != expected_enemy
        or visual.get("enemyAssetKey") != expected.get("enemySpriteAssetKey")
        or visual.get("enemyRefVaHex") != expected.get("enemySpriteRefVaHex")
        or visual.get("battleBackgroundRefVaHex") != expected.get("battleBackgroundRefVaHex")
        or visual.get("selectionKind") != "nearest-exe-resource-descriptor"
        or visual.get("originalEnemyRowBound") is not False
        or visual.get("originalStatsOrRewardsBound") is not False
        or state.get("enemyOriginalEnemyRowBound") is not False
        or state.get("enemyOriginalStatsOrRewardsBound") is not False
    ):
        raise WebDriverError(f"battle enemy sprite candidate was not preserved in runtime state: {state!r}")


def verify_battle_start_feedback(
    state: dict,
    expected: dict[str, str],
    *,
    field_encounter: bool = False,
) -> None:
    summary = state.get("summary") or {}
    summary_feedback = summary.get("battleStartFeedback") or {}
    feedback_log = state.get("battleStartFeedbackLog") or []
    feedback_render = state.get("battleStartFeedbackRender") or []
    feedback_last = state.get("battleStartFeedbackLast") or {}
    feedback_last_render = state.get("battleStartFeedbackLastRender") or {}
    summary_sound = summary.get("battleStartSound") or {}
    summary_feedback_sound = summary_feedback.get("sound") or {}
    feedback = next(
        (
            entry
            for entry in feedback_log
            if isinstance(entry, dict)
            and entry.get("source") == "battle-start-feedback"
            and entry.get("battleBackground") == expected["battleBackground"]
        ),
        {},
    )
    feedback_sound = feedback.get("sound") or {}
    rendered = next(
        (
            entry
            for entry in feedback_render
            if isinstance(entry, dict)
            and entry.get("source") == "battle-start-feedback"
            and entry.get("battleBackground") == expected["battleBackground"]
        ),
        {},
    )
    expected_text = f"전투 시작 {expected['battleBackground']}"
    expected_progress_id = expected.get("progressEventId", expected["blockId"])
    expected_source_backed = expected.get("sourceBacked", True)
    if (
        feedback.get("text") != expected_text
        or feedback.get("candidateId") != expected["candidateId"]
        or feedback.get("blockId") != expected["blockId"]
        or feedback.get("battleBackground") != expected["battleBackground"]
        or feedback.get("enemyName") != expected["enemyName"]
        or feedback.get("fieldEncounter") is not field_encounter
        or feedback.get("progressEventKind") != "battle-start"
        or feedback.get("progressEventId") != expected_progress_id
        or feedback.get("durationMs") != 1000
        or feedback.get("sourceBacked") is not expected_source_backed
        or feedback.get("browserBattleStartFeedbackImplemented") is not True
        or feedback.get("prototypeBattleStartImplemented") is not True
        or feedback.get("originalEventDrivenBattleEntry") is not False
        or feedback.get("originalStoryFlagRuntimeImplemented") is not False
        or not battle_start_feedback_sound_matches(feedback)
        or not sound_event_matches(feedback_sound, "battleStart", "/extract_wlk/08.wav")
        or (field_encounter and feedback.get("originalEncounterRuntimeImplemented") is not False)
        or rendered.get("text") != expected_text
        or rendered.get("active") is not True
        or rendered.get("candidateId") != expected["candidateId"]
        or rendered.get("blockId") != expected["blockId"]
        or rendered.get("battleBackground") != expected["battleBackground"]
        or rendered.get("fieldEncounter") is not field_encounter
        or rendered.get("progressEventKind") != "battle-start"
        or rendered.get("progressEventId") != expected_progress_id
        or rendered.get("durationMs") != 1000
        or rendered.get("browserBattleStartFeedbackImplemented") is not True
        or rendered.get("prototypeBattleStartImplemented") is not True
        or rendered.get("originalEventDrivenBattleEntry") is not False
        or rendered.get("originalStoryFlagRuntimeImplemented") is not False
        or not battle_start_feedback_sound_matches(rendered)
        or (field_encounter and rendered.get("originalEncounterRuntimeImplemented") is not False)
        or feedback_last.get("text") != expected_text
        or feedback_last_render.get("text") != expected_text
        or not battle_start_feedback_sound_matches(feedback_last)
        or not battle_start_feedback_sound_matches(feedback_last_render)
        or summary_feedback.get("text") != expected_text
        or summary_feedback.get("browserBattleStartFeedbackImplemented") is not True
        or not battle_start_feedback_sound_matches(summary_feedback)
        or not sound_event_matches(summary_feedback_sound, "battleStart", "/extract_wlk/08.wav")
        or not sound_event_matches(summary_sound, "battleStart", "/extract_wlk/08.wav")
    ):
        raise WebDriverError(f"battle start feedback was not rendered or logged: {state!r}")


def verify_battle_case(port: int, session_id: str, expected: dict[str, str]) -> tuple[dict, dict, int]:
    execute_js(port, session_id, prepare_battle_button_script(), timeout=3)
    button = wait_for_button_state(port, session_id)
    expected_text = f"전투 {expected['battleBackground']}"
    if (
        button.get("hidden") is not False
        or button.get("text") != expected_text
        or expected["blockId"] not in str(button.get("title") or "")
        or expected["battleBackground"] not in str(button.get("title") or "")
    ):
        raise WebDriverError(f"battle candidate button did not expose expected candidate: {button!r}")

    clicked = execute_js(port, session_id, click_battle_button_script(), timeout=3)
    if not clicked.get("ok") or clicked.get("before", {}).get("text") != expected_text:
        raise WebDriverError(f"battle candidate button click failed: {clicked!r}")
    wait_for_battle_scene(port, session_id)
    checksum = int(execute_js(port, session_id, canvas_checksum_script(), timeout=3))
    if checksum == 0:
        raise WebDriverError("battle candidate scene rendered a blank canvas")
    state = execute_js(port, session_id, battle_prototype_state_script(), timeout=3)
    summary = state.get("summary") or {}
    candidate = state.get("battleCandidate") or {}
    if (
        state.get("scene") != "battle"
        or summary.get("map") != expected["map"]
        or summary.get("candidateId") != expected["candidateId"]
        or summary.get("blockId") != expected["blockId"]
        or summary.get("battleBackground") != expected["battleBackground"]
        or summary.get("sourceBacked") is not True
        or summary.get("originalEventDrivenBattleEntry") is not False
        or candidate.get("id") != expected["candidateId"]
        or candidate.get("battleBackground") != expected["battleBackground"]
        or expected["battleBackground"] not in state.get("background", "")
        or expected["blockId"] not in " ".join(state.get("log") or [])
        or not (state.get("commands") or [""])[0]
    ):
        raise WebDriverError(f"battle prototype did not preserve candidate summary: {state!r}")
    verify_enemy_visual_state(state, expected)
    return button, state, checksum


def verify_battle_pointer_attack(state: dict, expected: dict[str, str]) -> None:
    effects = state.get("hitEffectLog") or []
    enemy_effect = next((effect for effect in effects if effect.get("targetType") == "enemy"), {})
    party_effect = next((effect for effect in effects if effect.get("targetType") == "party"), {})
    render_effect = state.get("hitEffectRender") or {}
    damage_texts = state.get("damageTextLog") or []
    enemy_text = next((entry for entry in damage_texts if entry.get("targetType") == "enemy"), {})
    party_text = next((entry for entry in damage_texts if entry.get("targetType") == "party"), {})
    damage_renders = state.get("damageTextRender") or []
    sound_state = state.get("soundState") or {}
    sound_counts = sound_state.get("counts") or {}
    sound_log = sound_state.get("log") or []
    sound_last = sound_state.get("last") or {}
    battle_hit_sounds = [
        entry
        for entry in sound_log
        if isinstance(entry, dict) and entry.get("key") == "battleHit"
    ]
    enemy_animation = state.get("enemyAttackAnimation") or {}
    enemy_animation_render = state.get("enemyAttackAnimationRender") or {}
    enemy_animation_log = state.get("enemyAttackAnimationLog") or []
    enemy_attack_review = state.get("enemyAttackReview") or {}
    battle_review_links = state.get("battleReviewLinks") or {}
    party_animation = state.get("partyActionAnimation") or {}
    party_animation_render = state.get("partyActionAnimationRender") or {}
    party_animation_log = state.get("partyActionAnimationLog") or []
    turn_result = state.get("turnResult") or {}
    turn_auto = state.get("turnAutoSave") or {}
    battle_prototype = state.get("battlePrototype") or {}
    battle_prototype_animation = battle_prototype.get("enemyAttackAnimationCandidate") or {}
    battle_prototype_party_animation = battle_prototype.get("partyActionAnimationCandidate") or {}
    expected_frames = expected.get("enemyAttackFrames") or [0, 1, 2, 3]

    def hit_sprite_metadata_ok(effect: dict, *, rendered: bool = False) -> bool:
        if (
            effect.get("effectAssetKey") != "battle_effects"
            or effect.get("effectSourceCns") != "btl_etc.cns"
            or effect.get("effectDescriptorTableVaHex") != "0x00442d95"
            or effect.get("effectDescriptorRow") != 3
            or effect.get("effectSourceY") != 64
            or effect.get("effectSourceWidth") != 64
            or effect.get("effectSourceHeight") != 32
            or effect.get("effectFrameCount") != 6
            or effect.get("browserBattleHitSpriteEffectImplemented") is not True
            or effect.get("originalBattleEffectTimingImplemented") is not False
        ):
            return False
        if not rendered:
            return True
        frame_index = effect.get("effectSpriteFrameIndex")
        if not isinstance(frame_index, int) or not 0 <= frame_index <= 5:
            return False
        return (
            effect.get("effectSpriteDrawn") is True
            and effect.get("effectSpriteSourceX") == frame_index * 64
            and effect.get("effectSpriteSourceY") == 64
            and effect.get("effectSpriteSourceWidth") == 64
            and effect.get("effectSpriteSourceHeight") == 32
            and isinstance(effect.get("effectSpriteDrawWidth"), int)
            and int(effect.get("effectSpriteDrawWidth") or 0) > 0
            and isinstance(effect.get("effectSpriteDrawHeight"), int)
            and int(effect.get("effectSpriteDrawHeight") or 0) > 0
        )

    def enemy_attack_animation_ok() -> bool:
        frames = enemy_animation.get("frames") or []
        render_frame = enemy_animation_render.get("frameIndex")
        if (
            enemy_animation.get("source") != "prototype-enemy-attack-animation"
            or enemy_animation.get("enemyName") != expected["enemyName"]
            or enemy_animation.get("enemyAssetKey") != expected["enemyAssetKey"]
            or enemy_animation.get("enemyCns") != expected["enemyCns"]
            or enemy_animation.get("actionId") != expected["enemyAttackActionId"]
            or enemy_animation.get("actionTitle") != expected["enemyActionName"]
            or enemy_animation.get("actionName") != expected["enemyActionName"]
            or enemy_animation.get("targetName") != "Ataho"
            or enemy_animation.get("targetIndex") != 0
            or enemy_animation.get("damage") != party_effect.get("damage")
            or frames != expected_frames
            or enemy_animation.get("durationMs") != 420
            or enemy_animation.get("lungePx") != 14
            or enemy_animation.get("browserBattleEnemyAttackAnimationImplemented") is not True
            or enemy_animation.get("prototypeMonsterAttackFrameRuntimeImplemented") is not True
            or enemy_animation.get("originalAttackSequenceBound") is not False
            or enemy_animation.get("originalEnemyAiImplemented") is not False
            or enemy_animation.get("originalCombatFormulaImplemented") is not False
            or enemy_animation.get("originalStoryFlagRuntimeImplemented") is not False
            or not isinstance(enemy_animation.get("frameCount"), int)
            or int(enemy_animation.get("frameCount") or 0) < len(expected_frames)
            or not enemy_animation_log
        ):
            return False
        if (
            enemy_animation_render.get("source") != "prototype-enemy-attack-animation-render"
            or enemy_animation_render.get("enemyName") != expected["enemyName"]
            or enemy_animation_render.get("enemyAssetKey") != expected["enemyAssetKey"]
            or enemy_animation_render.get("enemyCns") != expected["enemyCns"]
            or enemy_animation_render.get("actionId") != expected["enemyAttackActionId"]
            or enemy_animation_render.get("actionName") != expected["enemyActionName"]
            or enemy_animation_render.get("targetName") != "Ataho"
            or render_frame not in frames
            or enemy_animation_render.get("frames") != frames
            or not isinstance(enemy_animation_render.get("sourceWidth"), int)
            or int(enemy_animation_render.get("sourceWidth") or 0) <= 0
            or not isinstance(enemy_animation_render.get("sourceHeight"), int)
            or int(enemy_animation_render.get("sourceHeight") or 0) <= 0
            or not isinstance(enemy_animation_render.get("drawWidth"), int)
            or int(enemy_animation_render.get("drawWidth") or 0) <= 0
            or not isinstance(enemy_animation_render.get("drawHeight"), int)
            or int(enemy_animation_render.get("drawHeight") or 0) <= 0
            or enemy_animation_render.get("browserBattleEnemyAttackAnimationImplemented") is not True
            or enemy_animation_render.get("prototypeMonsterAttackFrameRuntimeImplemented") is not True
            or enemy_animation_render.get("enemyAttackSpriteDrawn") is not True
            or enemy_animation_render.get("originalAttackSequenceBound") is not False
            or enemy_animation_render.get("originalEnemyAiImplemented") is not False
        ):
            return False
        if (
            (turn_result.get("enemyAttackAnimation") or {}).get("actionId") != expected["enemyAttackActionId"]
            or turn_result.get("prototypeMonsterAttackFrameRuntimeImplemented") is not True
            or turn_auto.get("prototypeMonsterAttackFrameRuntimeImplemented") is not True
            or (turn_auto.get("enemyAttackAnimation") or {}).get("actionId") != expected["enemyAttackActionId"]
            or battle_prototype_animation.get("actionId") != expected["enemyAttackActionId"]
            or battle_prototype_animation.get("frames") != frames
            or battle_prototype_animation.get("browserBattleEnemyAttackAnimationImplemented") is not True
            or battle_prototype_animation.get("originalAttackSequenceBound") is not False
            or battle_prototype_animation.get("originalEnemyAiImplemented") is not False
        ):
            return False
        if (
            enemy_attack_review.get("source") != "battle-enemy-attack-review-hud"
            or enemy_attack_review.get("available") is not True
            or enemy_attack_review.get("enemyName") != expected["enemyName"]
            or enemy_attack_review.get("enemyAssetKey") != expected["enemyAssetKey"]
            or enemy_attack_review.get("enemyCns") != expected["enemyCns"]
            or enemy_attack_review.get("actionId") != expected["enemyAttackActionId"]
            or enemy_attack_review.get("actionTitle") != expected["enemyActionName"]
            or enemy_attack_review.get("actionName") != expected["enemyActionName"]
            or enemy_attack_review.get("frames") != frames
            or enemy_attack_review.get("activeFrameIndex") not in frames
            or enemy_attack_review.get("browserBattleEnemyAttackReviewHudImplemented") is not True
            or enemy_attack_review.get("prototypeMonsterAttackFrameRuntimeImplemented") is not True
            or enemy_attack_review.get("originalAttackSequenceBound") is not False
            or enemy_attack_review.get("originalEnemyAiImplemented") is not False
            or enemy_attack_review.get("originalCombatFormulaImplemented") is not False
        ):
            return False
        expected_battle_href = f"battle_analysis.html?{urlencode({'candidate': expected['candidateId']})}"
        expected_monster_href = (
            f"monster_review.html?{urlencode({'asset': expected['enemyAssetKey'], 'attack': expected['enemyAttackActionId']})}"
        )
        if (
            battle_review_links.get("source") != "battle-runtime-review-links"
            or battle_review_links.get("visible") is not True
            or battle_review_links.get("candidateId") != expected["candidateId"]
            or battle_review_links.get("battleBackground") != expected["battleBackground"]
            or battle_review_links.get("enemyName") != expected["enemyName"]
            or battle_review_links.get("enemyAssetKey") != expected["enemyAssetKey"]
            or battle_review_links.get("attackId") != expected["enemyAttackActionId"]
            or battle_review_links.get("battleHref") != expected_battle_href
            or battle_review_links.get("monsterHref") != expected_monster_href
            or state.get("battleReviewHref") != expected_battle_href
            or state.get("monsterReviewHref") != expected_monster_href
            or battle_review_links.get("browserBattleRuntimeReviewLinksImplemented") is not True
            or battle_review_links.get("originalEventDrivenBattleEntry") is not False
            or battle_review_links.get("originalEnemyAiImplemented") is not False
        ):
            return False
        return True

    def party_action_animation_ok() -> bool:
        frames = party_animation.get("frames") or []
        render_frame = party_animation_render.get("frameIndex")
        if (
            party_animation.get("source") != "prototype-party-action-animation"
            or party_animation.get("actorName") != "Ataho"
            or party_animation.get("actorIndex") != 0
            or party_animation.get("assetKey") != "battle_ataho"
            or party_animation.get("sourceCns") != "btl_at.cns"
            or party_animation.get("descriptorTableVaHex") != "0x00442d95"
            or party_animation.get("descriptorRow") != 4
            or party_animation.get("commandKind") != "attack"
            or party_animation.get("targetName") != expected["enemyName"]
            or party_animation.get("damage") != enemy_effect.get("damage")
            or party_animation.get("enemyHpBefore") != state.get("beforeHp")
            or party_animation.get("enemyHpAfter") != state.get("afterHp")
            or frames != [1, 2, 3, 4]
            or party_animation.get("frameWidth") != 64
            or party_animation.get("frameHeight") != 80
            or party_animation.get("durationMs") != 360
            or party_animation.get("lungePx") != 18
            or party_animation.get("browserBattlePartyActionAnimationImplemented") is not True
            or party_animation.get("prototypePartyActionFrameRuntimeImplemented") is not True
            or party_animation.get("originalPartyActionSequenceBound") is not False
            or party_animation.get("originalCombatFormulaImplemented") is not False
            or party_animation.get("originalStoryFlagRuntimeImplemented") is not False
            or not party_animation_log
        ):
            return False
        if (
            party_animation_render.get("source") != "prototype-party-action-animation-render"
            or party_animation_render.get("actorName") != "Ataho"
            or party_animation_render.get("actorIndex") != 0
            or party_animation_render.get("assetKey") != "battle_ataho"
            or party_animation_render.get("sourceCns") != "btl_at.cns"
            or party_animation_render.get("descriptorTableVaHex") != "0x00442d95"
            or party_animation_render.get("descriptorRow") != 4
            or party_animation_render.get("commandKind") != "attack"
            or party_animation_render.get("targetName") != expected["enemyName"]
            or render_frame not in frames
            or party_animation_render.get("frames") != frames
            or party_animation_render.get("sourceWidth") != 64
            or party_animation_render.get("sourceHeight") != 80
            or not isinstance(party_animation_render.get("drawWidth"), int)
            or int(party_animation_render.get("drawWidth") or 0) <= 0
            or not isinstance(party_animation_render.get("drawHeight"), int)
            or int(party_animation_render.get("drawHeight") or 0) <= 0
            or party_animation_render.get("browserBattlePartyActionAnimationImplemented") is not True
            or party_animation_render.get("prototypePartyActionFrameRuntimeImplemented") is not True
            or party_animation_render.get("partyActionSpriteDrawn") is not True
            or party_animation_render.get("originalPartyActionSequenceBound") is not False
            or party_animation_render.get("originalCombatFormulaImplemented") is not False
        ):
            return False
        if (
            (turn_result.get("playerActionAnimation") or {}).get("commandKind") != "attack"
            or turn_result.get("prototypePartyActionFrameRuntimeImplemented") is not True
            or turn_auto.get("prototypePartyActionFrameRuntimeImplemented") is not True
            or (turn_auto.get("playerActionAnimation") or {}).get("commandKind") != "attack"
            or battle_prototype_party_animation.get("commandKind") != "attack"
            or battle_prototype_party_animation.get("frames") != frames
            or battle_prototype_party_animation.get("browserBattlePartyActionAnimationImplemented") is not True
            or battle_prototype_party_animation.get("originalPartyActionSequenceBound") is not False
        ):
            return False
        return True

    if (
        state.get("ok") is not True
        or state.get("scene") != "battle"
        or state.get("hitIndex") != state.get("attackIndex")
        or not isinstance(state.get("beforeHp"), int)
        or not isinstance(state.get("afterHp"), int)
        or state.get("afterHp") >= state.get("beforeHp")
        or expected["enemyName"] not in " ".join(state.get("log") or [])
        or len(effects) < 2
        or enemy_effect.get("source") != "player-attack"
        or enemy_effect.get("attackerName") != "Ataho"
        or enemy_effect.get("targetName") != expected["enemyName"]
        or enemy_effect.get("hpBefore") != state.get("beforeHp")
        or enemy_effect.get("hpAfter") != state.get("afterHp")
        or enemy_effect.get("damage") != state.get("beforeHp") - state.get("afterHp")
        or enemy_effect.get("durationMs") != 260
        or enemy_effect.get("shakePx") != 4
        or enemy_effect.get("browserBattleHitEffectImplemented") is not True
        or not hit_sprite_metadata_ok(enemy_effect)
        or enemy_effect.get("originalCombatFormulaImplemented") is not False
        or enemy_effect.get("originalStoryFlagRuntimeImplemented") is not False
        or not sound_event_matches(enemy_effect.get("sound") or {}, "battleHit", "/extract_wlk/09.wav")
        or party_effect.get("source") != "enemy-attack"
        or party_effect.get("attackerName") != expected["enemyName"]
        or party_effect.get("targetType") != "party"
        or party_effect.get("targetName") != "Ataho"
        or not isinstance(party_effect.get("damage"), int)
        or party_effect.get("damage") <= 0
        or not sound_event_matches(party_effect.get("sound") or {}, "battleHit", "/extract_wlk/09.wav")
        or render_effect.get("targetType") != "party"
        or render_effect.get("targetName") != "Ataho"
        or render_effect.get("durationMs") != 260
        or render_effect.get("shakePx") != 4
        or render_effect.get("browserBattleHitEffectImplemented") is not True
        or not hit_sprite_metadata_ok(party_effect)
        or not hit_sprite_metadata_ok(render_effect, rendered=True)
        or len(damage_texts) < 2
        or enemy_text.get("targetName") != expected["enemyName"]
        or enemy_text.get("damage") != enemy_effect.get("damage")
        or enemy_text.get("text") != str(enemy_effect.get("damage"))
        or enemy_text.get("durationMs") != 620
        or enemy_text.get("browserBattleDamageTextImplemented") is not True
        or enemy_text.get("originalCombatFormulaImplemented") is not False
        or party_text.get("targetType") != "party"
        or party_text.get("targetName") != "Ataho"
        or party_text.get("damage") != party_effect.get("damage")
        or party_text.get("text") != str(party_effect.get("damage"))
        or party_text.get("durationMs") != 620
        or party_text.get("browserBattleDamageTextImplemented") is not True
        or len(damage_renders) < 2
        or not all(entry.get("browserBattleDamageTextImplemented") is True for entry in damage_renders)
        or int(sound_counts.get("battleHit") or 0) < 2
        or len(battle_hit_sounds) < 2
        or not sound_event_matches(sound_last, "battleHit", "/extract_wlk/09.wav")
        or not all(sound_event_matches(entry, "battleHit", "/extract_wlk/09.wav") for entry in battle_hit_sounds[:2])
        or not party_action_animation_ok()
        or not enemy_attack_animation_ok()
    ):
        raise WebDriverError(f"battle command pointer click did not execute attack/effect: {state!r}")


def verify_battle_skill_action(state: dict, expected: dict[str, str]) -> None:
    party_animation = state.get("partyActionAnimation") or {}
    party_animation_render = state.get("partyActionAnimationRender") or {}
    party_animation_log = state.get("partyActionAnimationLog") or []
    party_skill_frames = state.get("partySkillFrames") or []
    skill_use = state.get("skillUse") or {}
    skill_auto = state.get("skillAutoSave") or {}
    turn_result = state.get("turnResult") or {}
    turn_auto = state.get("turnAutoSave") or {}
    battle_prototype = state.get("battlePrototype") or {}
    battle_prototype_party_animation = battle_prototype.get("partyActionAnimationCandidate") or {}
    skill_command = state.get("skillCommand") or {}
    sound_state = state.get("soundState") or {}
    sound_counts = sound_state.get("counts") or {}
    sound_last = sound_state.get("last") or {}
    frames = party_animation.get("frames") or []
    render_frame = party_animation_render.get("frameIndex")

    if (
        state.get("ok") is not True
        or state.get("scene") != "battle"
        or state.get("actorName") != "Ataho"
        or skill_command.get("kind") != "skill"
        or skill_command.get("name") != "돌려차기"
        or skill_command.get("textTableKey") != "atahoActions"
        or skill_command.get("textTableIndex") != 1
        or state.get("beforeHp") != expected["enemyHp"]
        or state.get("afterHp") >= state.get("beforeHp")
        or state.get("beforeMp") != 8
        or state.get("afterMp") != 6
        or skill_use.get("source") != "prototype-battle-skill-effect"
        or skill_use.get("actorName") != "Ataho"
        or skill_use.get("skillName") != "돌려차기"
        or skill_use.get("skillBlock") != "individual"
        or skill_use.get("skillIndex") != 0
        or skill_use.get("skillLevel") != 1
        or skill_use.get("skillTextTableKey") != "atahoActions"
        or skill_use.get("skillTextTableIndex") != 1
        or skill_use.get("mpBefore") != 8
        or skill_use.get("mpAfter") != 6
        or skill_use.get("mpCost") != 2
        or skill_use.get("enemyName") != expected["enemyName"]
        or skill_use.get("enemyHpBefore") != expected["enemyHp"]
        or skill_use.get("enemyHpAfter") != state.get("afterHp")
        or skill_use.get("damage") != state.get("beforeHp") - state.get("afterHp")
        or skill_use.get("prototypeSkillUseImplemented") is not True
        or skill_use.get("prototypePartyActionFrameRuntimeImplemented") is not True
        or skill_use.get("originalSkillFormulaImplemented") is not False
        or skill_use.get("originalCombatFormulaImplemented") is not False
        or skill_use.get("originalStoryFlagRuntimeImplemented") is not False
        or int(sound_counts.get("battleHit") or 0) < 2
        or not sound_event_matches(sound_last, "battleHit", "/extract_wlk/09.wav")
    ):
        raise WebDriverError(f"battle skill command did not execute expected skill effect: {state!r}")

    if (
        party_animation.get("source") != "prototype-party-action-animation"
        or party_animation.get("actorName") != "Ataho"
        or party_animation.get("actorIndex") != 0
        or party_animation.get("assetKey") != "battle_ataho"
        or party_animation.get("sourceCns") != "btl_at.cns"
        or party_animation.get("descriptorTableVaHex") != "0x00442d95"
        or party_animation.get("descriptorRow") != 4
        or party_animation.get("commandName") != "돌려차기"
        or party_animation.get("commandKind") != "skill"
        or party_animation.get("targetName") != expected["enemyName"]
        or party_animation.get("damage") != skill_use.get("damage")
        or party_animation.get("mpCost") != 2
        or party_animation.get("enemyHpBefore") != state.get("beforeHp")
        or party_animation.get("enemyHpAfter") != state.get("afterHp")
        or frames != [4, 5, 6, 7]
        or party_animation.get("frameWidth") != 64
        or party_animation.get("frameHeight") != 80
        or party_animation.get("durationMs") != 360
        or party_animation.get("lungePx") != 18
        or party_animation.get("browserBattlePartyActionAnimationImplemented") is not True
        or party_animation.get("prototypePartyActionFrameRuntimeImplemented") is not True
        or party_animation.get("originalPartyActionSequenceBound") is not False
        or party_animation.get("originalCombatFormulaImplemented") is not False
        or party_animation.get("originalStoryFlagRuntimeImplemented") is not False
        or not party_animation_log
    ):
        raise WebDriverError(f"battle skill did not record party action animation: {state!r}")

    if (
        party_animation_render.get("source") != "prototype-party-action-animation-render"
        or party_animation_render.get("actorName") != "Ataho"
        or party_animation_render.get("actorIndex") != 0
        or party_animation_render.get("assetKey") != "battle_ataho"
        or party_animation_render.get("sourceCns") != "btl_at.cns"
        or party_animation_render.get("descriptorTableVaHex") != "0x00442d95"
        or party_animation_render.get("descriptorRow") != 4
        or party_animation_render.get("commandName") != "돌려차기"
        or party_animation_render.get("commandKind") != "skill"
        or party_animation_render.get("targetName") != expected["enemyName"]
        or render_frame not in frames
        or party_animation_render.get("frames") != frames
        or party_animation_render.get("sourceWidth") != 64
        or party_animation_render.get("sourceHeight") != 80
        or not isinstance(party_animation_render.get("drawWidth"), int)
        or int(party_animation_render.get("drawWidth") or 0) <= 0
        or not isinstance(party_animation_render.get("drawHeight"), int)
        or int(party_animation_render.get("drawHeight") or 0) <= 0
        or party_animation_render.get("browserBattlePartyActionAnimationImplemented") is not True
        or party_animation_render.get("prototypePartyActionFrameRuntimeImplemented") is not True
        or party_animation_render.get("partyActionSpriteDrawn") is not True
        or party_animation_render.get("originalPartyActionSequenceBound") is not False
        or party_animation_render.get("originalCombatFormulaImplemented") is not False
    ):
        raise WebDriverError(f"battle skill party action animation did not render: {state!r}")

    if (
        (skill_use.get("playerActionAnimation") or {}).get("commandKind") != "skill"
        or (skill_use.get("playerActionAnimation") or {}).get("frames") != frames
        or (skill_auto.get("playerActionAnimation") or {}).get("commandKind") != "skill"
        or skill_auto.get("prototypePartyActionFrameRuntimeImplemented") is not True
        or (turn_result.get("playerActionAnimation") or {}).get("commandKind") != "skill"
        or turn_result.get("prototypePartyActionFrameRuntimeImplemented") is not True
        or turn_auto.get("prototypePartyActionFrameRuntimeImplemented") is not True
        or (turn_auto.get("playerActionAnimation") or {}).get("commandKind") != "skill"
        or battle_prototype_party_animation.get("commandKind") != "skill"
        or battle_prototype_party_animation.get("frames") != frames
        or battle_prototype_party_animation.get("browserBattlePartyActionAnimationImplemented") is not True
        or battle_prototype_party_animation.get("originalPartyActionSequenceBound") is not False
    ):
        raise WebDriverError(f"battle skill did not preserve party action animation in payloads: {state!r}")

    expected_party_frames = {
        "Ataho": {
            "asset": "battle_ataho",
            "source_cns": "btl_at.cns",
            "descriptor_row": 4,
            "height": 80,
            "text_table": "atahoActions",
            "text_index": 1,
            "mp_before": 8,
            "mp_after": 6,
        },
        "Rinshan": {
            "asset": "battle_rinshan",
            "source_cns": "btl_rs.cns",
            "descriptor_row": 5,
            "height": 80,
            "text_table": "skillNamesB",
            "text_index": 7,
            "mp_before": 14,
            "mp_after": 11,
        },
        "Smashu": {
            "asset": "battle_smash",
            "source_cns": "btl_sm.cns",
            "descriptor_row": 6,
            "height": 48,
            "text_table": "actionSetB",
            "text_index": 1,
            "mp_before": 4,
            "mp_after": 2,
        },
    }
    by_member = {
        str(entry.get("memberName") or ""): entry
        for entry in party_skill_frames
        if isinstance(entry, dict)
    }
    if set(by_member) != set(expected_party_frames):
        raise WebDriverError(f"battle skill action did not capture all party skill frame snapshots: {state!r}")
    for member_name, spec in expected_party_frames.items():
        entry = by_member[member_name]
        animation = entry.get("partyActionAnimation") or {}
        rendered = entry.get("partyActionAnimationRender") or {}
        skill = entry.get("skillUse") or {}
        auto = entry.get("skillAutoSave") or {}
        turn = entry.get("turnResult") or {}
        turn_save = entry.get("turnAutoSave") or {}
        command = entry.get("skillCommand") or {}
        frame_run = animation.get("frames") or []
        if (
            entry.get("ok") is not True
            or command.get("kind") != "skill"
            or command.get("textTableKey") != spec["text_table"]
            or command.get("textTableIndex") != spec["text_index"]
            or entry.get("beforeMp") != spec["mp_before"]
            or entry.get("afterMp") != spec["mp_after"]
            or entry.get("afterHp") >= entry.get("beforeHp")
            or skill.get("source") != "prototype-battle-skill-effect"
            or skill.get("actorName") != member_name
            or skill.get("skillTextTableKey") != spec["text_table"]
            or skill.get("skillTextTableIndex") != spec["text_index"]
            or skill.get("prototypeSkillUseImplemented") is not True
            or skill.get("prototypePartyActionFrameRuntimeImplemented") is not True
            or skill.get("originalSkillFormulaImplemented") is not False
            or animation.get("source") != "prototype-party-action-animation"
            or animation.get("actorName") != member_name
            or animation.get("assetKey") != spec["asset"]
            or animation.get("sourceCns") != spec["source_cns"]
            or animation.get("descriptorRow") != spec["descriptor_row"]
            or animation.get("commandKind") != "skill"
            or frame_run != [4, 5, 6, 7]
            or animation.get("frameWidth") != 64
            or animation.get("frameHeight") != spec["height"]
            or animation.get("browserBattlePartyActionAnimationImplemented") is not True
            or animation.get("prototypePartyActionFrameRuntimeImplemented") is not True
            or animation.get("originalPartyActionSequenceBound") is not False
            or rendered.get("source") != "prototype-party-action-animation-render"
            or rendered.get("actorName") != member_name
            or rendered.get("assetKey") != spec["asset"]
            or rendered.get("sourceCns") != spec["source_cns"]
            or rendered.get("descriptorRow") != spec["descriptor_row"]
            or rendered.get("commandKind") != "skill"
            or rendered.get("frames") != frame_run
            or rendered.get("sourceWidth") != 64
            or rendered.get("sourceHeight") != spec["height"]
            or rendered.get("partyActionSpriteDrawn") is not True
            or rendered.get("browserBattlePartyActionAnimationImplemented") is not True
            or rendered.get("prototypePartyActionFrameRuntimeImplemented") is not True
            or (skill.get("playerActionAnimation") or {}).get("frames") != frame_run
            or (auto.get("playerActionAnimation") or {}).get("commandKind") != "skill"
            or auto.get("prototypePartyActionFrameRuntimeImplemented") is not True
            or (turn.get("playerActionAnimation") or {}).get("commandKind") != "skill"
            or turn.get("prototypePartyActionFrameRuntimeImplemented") is not True
            or turn_save.get("prototypePartyActionFrameRuntimeImplemented") is not True
        ):
            raise WebDriverError(f"battle skill party frame snapshot failed for {member_name}: {state!r}")


def verify_battle_item_target_selection(state: dict) -> None:
    open_marker = state.get("openMarker") or {}
    selected_marker = state.get("selectedMarker") or {}
    selection_log = state.get("selectionLog") or []
    item_use = state.get("itemUse") or {}
    item_feedback = item_use.get("itemFeedback") or {}
    item_auto = state.get("itemAutoSave") or {}
    turn_result = state.get("turnResult") or {}
    turn_auto = state.get("turnAutoSave") or {}
    objective_before = state.get("objectiveBefore") or {}
    objective_action = state.get("objectiveAction") or {}
    objective_notice = state.get("objectiveCompletionNotice") or {}
    objective_notice_lines = " ".join(objective_notice.get("lines") or [])
    objective_notice_completion = objective_notice.get("completion") or {}
    objective_dialogue = state.get("objectiveActiveDialogueBlock") or {}
    objective_feedback = state.get("objectiveItemFeedbackLast") or {}
    objective_feedback_render = state.get("objectiveItemFeedbackLastRender") or {}
    objective_sound_counts = ((state.get("objectiveSoundState") or {}).get("counts") or {})
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    before_party = {row.get("name"): row for row in state.get("beforeParty") or []}
    after_party = {row.get("name"): row for row in state.get("afterParty") or []}
    runtime_characters = {row.get("name"): row for row in state.get("runtimeCharacters") or []}
    runtime_items = {row.get("key"): row for row in state.get("runtimeItems") or []}
    saved = state.get("savedPayload") or {}
    saved_runtime = saved.get("runtimeState") or {}
    saved_characters = {row.get("name"): row for row in saved_runtime.get("characters") or []}
    saved_items = {row.get("key"): row for row in saved_runtime.get("items") or []}
    menu_state = state.get("battleMenuState") or {}
    target_choices = state.get("targetChoices") or []
    if (
        state.get("ok") is not True
        or state.get("scene") != "battle"
        or state.get("map") != "map1_02b"
        or state.get("openResult") is not True
        or state.get("selectResult") is not True
        or state.get("actorName") != "Ataho"
        or state.get("targetIndex") < 0
        or not any(choice.get("memberName") == "Rinshan" for choice in target_choices)
        or open_marker.get("source") != "battle-item-target-selection"
        or open_marker.get("phase") != "open"
        or open_marker.get("itemKey") != "herb"
        or open_marker.get("targetCount") != 3
        or open_marker.get("prototypeBattleItemTargetSelectionImplemented") is not True
        or open_marker.get("originalItemEffectFormulaImplemented") is not False
        or selected_marker.get("source") != "battle-item-target-selection"
        or selected_marker.get("phase") != "selected"
        or selected_marker.get("itemKey") != "herb"
        or selected_marker.get("selectedTargetName") != "Rinshan"
        or selected_marker.get("targetCount") != 3
        or selected_marker.get("prototypeBattleItemTargetSelectionImplemented") is not True
        or len(selection_log) < 2
        or [entry.get("phase") for entry in selection_log[-2:]] != ["open", "selected"]
        or before_party.get("Rinshan", {}).get("hp") != 8
        or after_party.get("Rinshan", {}).get("hp") != 28
        or runtime_characters.get("Rinshan", {}).get("hp") != 28
        or runtime_items.get("herb", {}).get("count") != 1
        or saved.get("map") != "map1_02b"
        or saved_characters.get("Rinshan", {}).get("hp") != 28
        or saved_items.get("herb", {}).get("count") != 1
        or item_use.get("source") != "prototype-battle-item-effect"
        or item_use.get("prototypeItemUseImplemented") is not True
        or item_use.get("itemKey") != "herb"
        or item_use.get("targetName") != "Rinshan"
        or item_use.get("hpBefore") != 8
        or item_use.get("hpAfter") != 28
        or item_use.get("countBefore") != 2
        or item_use.get("countAfter") != 1
        or item_use.get("originalItemEffectFormulaImplemented") is not False
        or item_use.get("originalStoryFlagRuntimeImplemented") is not False
        or item_feedback.get("source") != "battle-item-use"
        or item_feedback.get("targetName") != "Rinshan"
        or item_feedback.get("hpGain") != 20
        or item_feedback.get("battleItemSoundPlayed") is not True
        or item_auto.get("saved") is not True
        or item_auto.get("source") != "item-use-prototype"
        or item_auto.get("scope") != "battle-item"
        or item_auto.get("targetName") != "Rinshan"
        or turn_result.get("source") != "prototype-battle-turn-result"
        or turn_result.get("commandKey") != "item:herb"
        or turn_result.get("commandName") != item_use.get("itemName")
        or turn_result.get("actorName") != "Ataho"
        or turn_result.get("prototypeBattleTurnImplemented") is not True
        or turn_result.get("originalCombatFormulaImplemented") is not False
        or turn_auto.get("saved") is not True
        or turn_auto.get("source") != "battle-turn-prototype"
        or turn_auto.get("scope") != "battle-turn"
        or counts.get("item-use-prototype") != 1
        or counts.get("battle-turn-prototype") != 1
        or state.get("objectiveResult") is not True
        or objective_before.get("title") != "후보 전투 도구 완료 btl_b1"
        or objective_before.get("nextAction") != "완료 알림 확인"
        or objective_before.get("detail") != "Ataho / 약초 / Rinshan / 2->1"
        or objective_before.get("source") != "prototype-battle-item-completion"
        or objective_action.get("action") != "battle-item-completion-notice"
        or objective_action.get("activeId") != "battle-item-complete:map1_02b"
        or objective_action.get("prototypeItemUseImplemented") is not True
        or objective_action.get("originalItemEffectFormulaImplemented") is not False
        or objective_action.get("originalStoryFlagRuntimeImplemented") is not False
        or objective_notice.get("blockId") != "battle-item-complete:map1_02b"
        or objective_notice.get("map") != "map1_02b"
        or "전투 도구 완료 1/1" not in objective_notice_lines
        or "Ataho 약초" not in objective_notice_lines
        or "약초 2->1" not in objective_notice_lines
        or "Rinshan HP 8->28" not in objective_notice_lines
        or objective_notice_completion.get("completed") is not True
        or objective_notice_completion.get("battleBackground") != "btl_b1"
        or objective_notice_completion.get("actorName") != "Ataho"
        or objective_notice_completion.get("itemName") != "약초"
        or objective_notice_completion.get("targetName") != "Rinshan"
        or objective_notice_completion.get("countBefore") != 2
        or objective_notice_completion.get("countAfter") != 1
        or objective_notice_completion.get("hpBefore") != 8
        or objective_notice_completion.get("hpAfter") != 28
        or objective_notice_completion.get("prototypeItemUseImplemented") is not True
        or objective_notice_completion.get("originalItemEffectFormulaImplemented") is not False
        or objective_notice_completion.get("originalStoryFlagRuntimeImplemented") is not False
        or objective_dialogue.get("blockId") != "battle-item-complete:map1_02b"
        or objective_dialogue.get("line") != "전투 도구 완료 1/1"
        or objective_feedback.get("source") != "battle-item-completion-notice-feedback"
        or objective_feedback.get("text") != "전투 도구 완료 1/1"
        or objective_feedback.get("itemKey") != "herb"
        or objective_feedback.get("itemName") != "약초"
        or objective_feedback.get("targetName") != "Rinshan"
        or objective_feedback.get("hpBefore") != 8
        or objective_feedback.get("hpAfter") != 28
        or objective_feedback.get("countBefore") != 2
        or objective_feedback.get("countAfter") != 1
        or objective_feedback.get("battleItemSound") != "menuConfirm"
        or objective_feedback.get("battleItemSoundSrc") != "../extract_wlk/04.wav"
        or objective_feedback.get("battleItemSoundPlayed") is not True
        or objective_feedback.get("browserBattleItemFeedbackImplemented") is not True
        or objective_feedback_render.get("active") is not True
        or objective_feedback_render.get("source") != "battle-item-completion-notice-feedback"
        or objective_feedback_render.get("text") != "전투 도구 완료 1/1"
        or objective_feedback_render.get("battleItemSound") != "menuConfirm"
        or objective_feedback_render.get("battleItemSoundSrc") != "../extract_wlk/04.wav"
        or objective_feedback_render.get("battleItemSoundPlayed") is not True
        or int(objective_sound_counts.get("menuConfirm") or 0) < 1
        or menu_state.get("itemMenuOpen") is not False
        or menu_state.get("targetMenuOpen") is not False
        or menu_state.get("pendingBattleItem") is not None
        or menu_state.get("pendingBattleTarget") is not None
        or menu_state.get("activeActorIndex") != 1
        or state.get("originalItemEffectFormulaImplemented") is not False
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"battle item target selection did not apply to selected party member: {state!r}")


def verify_battle_action_state(state: dict, expected: dict[str, str]) -> None:
    marker = state.get("marker") or {}
    prompt = marker.get("promptBefore") or {}
    summary = state.get("summary") or {}
    candidate = state.get("battleCandidate") or {}
    progress = state.get("progress") or {}
    if (
        marker.get("actionResult") is not True
        or marker.get("map") != expected["map"]
        or prompt.get("kind") != "battle-candidate"
        or prompt.get("text") != f"Enter -> 전투 {expected['battleBackground']}"
        or prompt.get("id") != expected["blockId"]
        or prompt.get("battleBackground") != expected["battleBackground"]
        or prompt.get("completed") is not False
        or prompt.get("originalEventDrivenBattleEntry") is not False
        or state.get("scene") != "battle"
        or summary.get("map") != expected["map"]
        or summary.get("candidateId") != expected["candidateId"]
        or summary.get("blockId") != expected["blockId"]
        or summary.get("battleBackground") != expected["battleBackground"]
        or summary.get("sourceBacked") is not True
        or summary.get("originalEventDrivenBattleEntry") is not False
        or candidate.get("id") != expected["candidateId"]
        or candidate.get("battleBackground") != expected["battleBackground"]
        or expected["blockId"] not in " ".join(state.get("log") or [])
        or (progress.get("counts") or {}).get("battle-start") != 1
    ):
        raise WebDriverError(f"battle action path did not preserve candidate summary: {state!r}")
    verify_battle_start_feedback(state, expected)
    verify_enemy_visual_state(state, expected)


def verify_battle_candidate_menu_state(state: dict) -> None:
    marker = state.get("marker") or {}
    before = marker.get("before") or {}
    menu_marker = state.get("menuMarker") or {}
    selection = state.get("selection") or {}
    summary = state.get("summary") or {}
    candidate = state.get("battleCandidate") or {}
    visual = state.get("enemyVisual") or {}
    labels = [str(label) for label in before.get("labels") or []]
    if (
        marker.get("ok") is not True
        or before.get("menuMode") != "battle-candidate"
        or before.get("count", 0) < 2
        or before.get("targetBackground") != "btl_j1"
        or before.get("targetCandidateId") != "event-dialogue-block-010:btl_j1"
        or before.get("targetEnemy") != "황야 괴수"
        or before.get("targetSprite") != "zkb_hyr.cns"
        or not any("btl_j1 황야 괴수" in label for label in labels)
        or marker.get("commandResult") is not True
        or marker.get("afterMenuMode") != "main"
        or marker.get("afterMenuOpen") is not False
        or menu_marker.get("source") != "prototype-battle-candidate-menu"
        or menu_marker.get("map") != "map2_14j"
        or menu_marker.get("family") != "j"
        or menu_marker.get("count", 0) < 2
        or menu_marker.get("originalEventDrivenBattleEntry") is not False
        or selection.get("source") != "prototype-battle-candidate-menu"
        or selection.get("map") != "map2_14j"
        or selection.get("candidateId") != "event-dialogue-block-010:btl_j1"
        or selection.get("blockId") != "event-dialogue-block-010"
        or selection.get("battleBackground") != "btl_j1"
        or selection.get("enemyName") != "황야 괴수"
        or selection.get("enemySprite") != "zkb_hyr.cns"
        or selection.get("originalEventDrivenBattleEntry") is not False
        or selection.get("originalEnemyRowBound") is not False
        or state.get("scene") != "battle"
        or summary.get("map") != "map2_14j"
        or summary.get("candidateId") != "event-dialogue-block-010:btl_j1"
        or summary.get("blockId") != "event-dialogue-block-010"
        or summary.get("battleBackground") != "btl_j1"
        or summary.get("enemyName") != "황야 괴수"
        or summary.get("enemySpriteCandidate") != "zkb_hyr.cns"
        or summary.get("enemySpriteAssetKey") != "zkb_hyr"
        or summary.get("enemyProfileSource") != "prototype-enemy-profile"
        or summary.get("originalEventDrivenBattleEntry") is not False
        or summary.get("originalEnemyRowBound") is not False
        or candidate.get("id") != "event-dialogue-block-010:btl_j1"
        or visual.get("enemyCns") != "zkb_hyr.cns"
    ):
        raise WebDriverError(f"battle candidate menu selection did not start the requested battle: {state!r}")
    verify_battle_start_feedback(state, {
        "map": "map2_14j",
        "candidateId": "event-dialogue-block-010:btl_j1",
        "blockId": "event-dialogue-block-010",
        "battleBackground": "btl_j1",
        "enemyName": "황야 괴수",
    })


def verify_battle_sprite_menu_state(state: dict) -> None:
    marker = state.get("marker") or {}
    before = marker.get("before") or {}
    menu_marker = state.get("menuMarker") or {}
    selection = state.get("selection") or {}
    summary = state.get("summary") or {}
    candidate = state.get("battleCandidate") or {}
    profile = state.get("enemyProfile") or {}
    visual = state.get("enemyVisual") or {}
    labels = [str(label) for label in before.get("labels") or []]
    if (
        marker.get("ok") is not True
        or before.get("menuMode") != "battle-candidate"
        or before.get("count") != 70
        or before.get("targetCandidateId") != "sprite-only:zk_big"
        or before.get("targetBackground") != "btl_j1"
        or before.get("targetEnemy") != "마수 zk_big"
        or before.get("targetSprite") != "zk_big.cns"
        or before.get("targetAsset") != "zk_big"
        or before.get("targetSource") != "prototype-battle-sprite-menu"
        or not any("마수 zk_big" in label for label in labels)
        or marker.get("commandResult") is not True
        or marker.get("afterMenuMode") != "main"
        or marker.get("afterMenuOpen") is not False
        or menu_marker.get("source") != "prototype-battle-candidate-menu"
        or menu_marker.get("map") != "map2_14j"
        or menu_marker.get("family") != "j"
        or menu_marker.get("count") != 69
        or menu_marker.get("eventCandidateCount") != 4
        or menu_marker.get("spriteCandidateCount") != 65
        or menu_marker.get("prototypeSpriteOnlyBattleImplemented") is not True
        or menu_marker.get("originalEventDrivenBattleEntry") is not False
        or menu_marker.get("originalEnemyRowBound") is not False
        or selection.get("source") != "prototype-battle-sprite-menu"
        or selection.get("map") != "map2_14j"
        or selection.get("candidateId") != "sprite-only:zk_big"
        or selection.get("blockId") != ""
        or selection.get("battleBackground") != "btl_j1"
        or selection.get("enemyName") != "마수 zk_big"
        or selection.get("enemySprite") != "zk_big.cns"
        or selection.get("enemyAssetKey") != "zk_big"
        or selection.get("spriteOnly") is not True
        or selection.get("prototypeSpriteOnlyBattleImplemented") is not True
        or selection.get("originalEventDrivenBattleEntry") is not False
        or selection.get("originalEnemyRowBound") is not False
        or state.get("scene") != "battle"
        or summary.get("map") != "map2_14j"
        or summary.get("candidateId") != "sprite-only:zk_big"
        or summary.get("blockId") != ""
        or summary.get("battleBackground") != "btl_j1"
        or summary.get("enemyName") != "마수 zk_big"
        or summary.get("enemySpriteCandidate") != "zk_big.cns"
        or summary.get("enemySpriteAssetKey") != "zk_big"
        or summary.get("enemyProfileSource") != "generated-extracted-sprite-profile"
        or summary.get("battleEnemySpriteSelectionKind") != "extracted-sprite-only-prototype"
        or summary.get("sourceBacked") is not False
        or summary.get("prototypeSpriteOnlyBattleImplemented") is not True
        or summary.get("originalEventDrivenBattleEntry") is not False
        or summary.get("originalEnemyRowBound") is not False
        or summary.get("originalStatsOrRewardsBound") is not False
        or summary.get("originalRewardTableMapped") is not False
        or summary.get("originalDropTableMapped") is not False
        or candidate.get("id") != "sprite-only:zk_big"
        or candidate.get("blockId") != ""
        or candidate.get("battleBackground") != "btl_j1"
        or candidate.get("enemySpriteOnly") is not True
        or candidate.get("enemyCns") != "zk_big.cns"
        or candidate.get("enemyAssetKey") != "zk_big"
        or profile.get("source") != "generated-extracted-sprite-profile"
        or profile.get("assetKey") != "zk_big"
        or profile.get("enemyCns") != "zk_big.cns"
        or profile.get("prototypeSpriteOnlyBattleImplemented") is not True
        or visual.get("enemyCns") != "zk_big.cns"
        or visual.get("enemyAssetKey") != "zk_big"
        or visual.get("selectionKind") != "extracted-sprite-only-prototype"
        or visual.get("prototypeSpriteOnlyBattleImplemented") is not True
        or visual.get("originalEnemyRowBound") is not False
    ):
        raise WebDriverError(f"sprite-only battle menu selection did not start the requested battle: {state!r}")
    verify_battle_start_feedback(state, {
        "map": "map2_14j",
        "candidateId": "sprite-only:zk_big",
        "blockId": "",
        "battleBackground": "btl_j1",
        "enemyName": "마수 zk_big",
        "progressEventId": "sprite-only:zk_big",
        "sourceBacked": False,
    })


def verify_battle_sprite_direct_state(state: dict) -> None:
    summary = state.get("summary") or {}
    candidate = state.get("battleCandidate") or {}
    profile = state.get("enemyProfile") or {}
    visual = state.get("enemyVisual") or {}
    if (
        state.get("scene") != "battle"
        or "battleCandidate=sprite-only%3Azk_big" not in str(state.get("href") or "")
        or summary.get("map") != "map2_14j"
        or summary.get("requestedBattleCandidate") != "sprite-only:zk_big"
        or summary.get("candidateId") != "sprite-only:zk_big"
        or summary.get("blockId") != ""
        or summary.get("battleBackground") != "btl_j1"
        or summary.get("enemyName") != "마수 zk_big"
        or summary.get("enemySpriteCandidate") != "zk_big.cns"
        or summary.get("enemySpriteAssetKey") != "zk_big"
        or summary.get("enemyProfileSource") != "generated-extracted-sprite-profile"
        or summary.get("battleEnemySpriteSelectionKind") != "extracted-sprite-only-prototype"
        or summary.get("sourceBacked") is not False
        or summary.get("prototypeSpriteOnlyBattleImplemented") is not True
        or summary.get("originalEventDrivenBattleEntry") is not False
        or summary.get("originalEnemyRowBound") is not False
        or summary.get("originalStatsOrRewardsBound") is not False
        or summary.get("originalRewardTableMapped") is not False
        or summary.get("originalDropTableMapped") is not False
        or candidate.get("id") != "sprite-only:zk_big"
        or candidate.get("blockId") != ""
        or candidate.get("battleBackground") != "btl_j1"
        or candidate.get("enemySpriteOnly") is not True
        or candidate.get("enemyCns") != "zk_big.cns"
        or candidate.get("enemyAssetKey") != "zk_big"
        or profile.get("source") != "generated-extracted-sprite-profile"
        or profile.get("assetKey") != "zk_big"
        or profile.get("enemyCns") != "zk_big.cns"
        or profile.get("prototypeSpriteOnlyBattleImplemented") is not True
        or visual.get("enemyCns") != "zk_big.cns"
        or visual.get("enemyAssetKey") != "zk_big"
        or visual.get("selectionKind") != "extracted-sprite-only-prototype"
        or visual.get("prototypeSpriteOnlyBattleImplemented") is not True
        or visual.get("originalEnemyRowBound") is not False
    ):
        raise WebDriverError(f"direct sprite-only battle URL did not start the requested battle: {state!r}")
    verify_battle_start_feedback(state, {
        "map": "map2_14j",
        "candidateId": "sprite-only:zk_big",
        "blockId": "",
        "battleBackground": "btl_j1",
        "enemyName": "마수 zk_big",
        "progressEventId": "sprite-only:zk_big",
        "sourceBacked": False,
    })


def verify_dialogue_battle_link_state(state: dict) -> None:
    expected = {
        "map": "map1_02b",
        "candidateId": "event-dialogue-block-015:btl_b1",
        "blockId": "event-dialogue-block-015",
        "battleBackground": "btl_b1",
        "enemySpriteCandidate": "zky_ao.cns",
        "enemySpriteAssetKey": "zky_ao",
        "enemySpriteRefVaHex": "0x00490124",
        "battleBackgroundRefVaHex": "0x0048f0bc",
        "enemyName": "푸른 마수",
        "enemyHp": 42,
        "enemyAtk": 4,
        "enemyDef": 0,
        "enemyActionName": "할퀴기",
        "enemyActionIndex": 3,
        "enemyRewardExp": 7,
        "enemyDropKey": "herb",
        "enemyDropName": "약초",
        "enemyDropCount": 1,
    }
    link = state.get("link") or {}
    objective = state.get("objectiveBefore") or {}
    action_prompt = state.get("actionPromptBefore") or {}
    link_action = state.get("dialogueBattleLinkAction") or {}
    summary = state.get("summary") or {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    candidate = state.get("battleCandidate") or {}
    auto_save = state.get("autoSave") or {}
    saved_payload = state.get("savedPayload") or {}
    saved_counts = ((saved_payload.get("prototypeProgress") or {}).get("counts") or {})
    if (
        state.get("started") is not True
        or state.get("scene") != "battle"
        or link.get("source") != "prototype-dialogue-battle-link"
        or link.get("blockId") != expected["blockId"]
        or link.get("candidateId") != expected["candidateId"]
        or link.get("battleBackground") != expected["battleBackground"]
        or link.get("prototypeDialogueBattleLinkImplemented") is not True
        or link.get("originalEventDrivenBattleEntry") is not False
        or link.get("originalEventVmRuntimeImplemented") is not False
        or link.get("originalStoryFlagRuntimeImplemented") is not False
        or ((link.get("progressEvent") or {}).get("kind")) != "dialogue-battle-link"
        or ((link.get("progressEvent") or {}).get("id")) != expected["blockId"]
        or ((link.get("progressEvent") or {}).get("detail") or {}).get("dialogueCompleteEventKind") != "dialogue-complete"
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "dialogue-battle-link"
        or auto_save.get("blockId") != expected["blockId"]
        or auto_save.get("candidateId") != expected["candidateId"]
        or auto_save.get("battleBackground") != expected["battleBackground"]
        or saved_payload.get("map") != expected["map"]
        or saved_counts.get("dialogue-complete") != 1
        or saved_counts.get("dialogue-battle-link") != 1
        or objective.get("title") != "후보 대사 전투 btl_b1"
        or objective.get("nextAction") != "전투 시작"
        or objective.get("source") != "prototype-dialogue-battle-link"
        or action_prompt.get("kind") != "dialogue-battle-link"
        or action_prompt.get("text") != "Enter -> 대사 전투 btl_b1"
        or action_prompt.get("source") != "prototype-dialogue-battle-link"
        or action_prompt.get("blockId") != expected["blockId"]
        or action_prompt.get("candidateId") != expected["candidateId"]
        or action_prompt.get("battleBackground") != expected["battleBackground"]
        or action_prompt.get("prototypeDialogueBattleLinkImplemented") is not True
        or state.get("actionResult") is not True
        or link_action.get("action") != "dialogue-battle-action"
        or link_action.get("handled") is not True
        or link_action.get("activeId") != expected["blockId"]
        or link_action.get("candidateId") != expected["candidateId"]
        or link_action.get("battleBackground") != expected["battleBackground"]
        or link_action.get("source") != "prototype-dialogue-battle-link"
        or link_action.get("prototypeDialogueBattleLinkImplemented") is not True
        or link_action.get("originalEventDrivenBattleEntry") is not False
        or summary.get("map") != expected["map"]
        or summary.get("candidateId") != expected["candidateId"]
        or summary.get("blockId") != expected["blockId"]
        or summary.get("battleBackground") != expected["battleBackground"]
        or summary.get("dialogueBattleLink") is not True
        or summary.get("dialogueBlockId") != expected["blockId"]
        or summary.get("prototypeDialogueBattleLinkImplemented") is not True
        or summary.get("originalEventDrivenBattleEntry") is not False
        or candidate.get("id") != expected["candidateId"]
        or counts.get("dialogue-complete") != 1
        or counts.get("dialogue-battle-link") != 1
        or counts.get("battle-start") != 1
    ):
        raise WebDriverError(f"dialogue battle link did not start the expected battle: {state!r}")
    verify_battle_vm_replay(link.get("vmReplay") or {}, expected["blockId"])
    verify_battle_vm_replay(summary.get("vmReplay") or {}, expected["blockId"])
    verify_battle_start_feedback(state, expected)
    verify_enemy_visual_state(state, expected)


def verify_battle_completion_state(state: dict, restored: bool = False) -> None:
    completion = state.get("completion") or {}
    battle = completion.get("battle") or {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    battle_story_key = "story:battle-victory:map1_02b:event-dialogue-block-015"
    progress_story_flags = progress.get("storyFlags") or {}
    button_text = state.get("buttonText") or ""
    progress_review_lines = "\n".join((state.get("progressReviewBlock") or {}).get("lines") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or state.get("loadedSaveSummary") is not None
        or button_text != "전투 완료 btl_b1"
        or "완료 event-dialogue-block-015" not in str(state.get("buttonTitle") or "")
        or battle.get("id") != "event-dialogue-block-015"
        or battle.get("completed") is not True
        or battle.get("completedCount") != 1
        or battle.get("remainingCount") != 0
        or battle.get("battleBackground") != "btl_b1"
        or battle.get("originalEventDrivenBattleEntry") is not False
        or battle.get("originalStoryFlagRuntimeImplemented") is not False
        or completion.get("originalStoryFlagRuntimeImplemented") is not False
        or counts.get("battle-victory") != 1
        or progress.get("originalStoryFlagRuntimeImplemented") is not False
        or battle_story_key not in story_flag_keys(progress_story_flags)
        or "전투 보상 소지금 75 / 경험치 +7" not in progress_review_lines
        or "전투 드롭 약초 2" not in progress_review_lines
        or "전투 레벨 Ataho Lv2 3/125" not in progress_review_lines
    ):
        raise WebDriverError(f"candidate battle completion state is incomplete: {state!r}")
    verify_story_flags(
        progress_story_flags,
        {battle_story_key},
        {"battle-victory": 1},
        "candidate battle completion progress",
    )
    if restored:
        commands = state.get("menuCommands") or []
        command_names = {row.get("command"): row.get("name") for row in commands}
        completion_feedback_log = state.get("battleCompletionFeedbackLog") or []
        completion_feedback_render = state.get("battleCompletionFeedbackRender") or []
        completion_feedback_last = state.get("battleCompletionFeedbackLast") or {}
        completion_feedback_last_render = state.get("battleCompletionFeedbackLastRender") or {}
        completion_feedback_entry = next(
            (
                entry
                for entry in completion_feedback_log
                if isinstance(entry, dict)
                and entry.get("source") == "battle-completion-feedback"
                and entry.get("text") == "전투 완료 btl_b1"
            ),
            {},
        )
        completion_feedback_render_entry = next(
            (
                entry
                for entry in completion_feedback_render
                if isinstance(entry, dict)
                and entry.get("source") == "battle-completion-feedback"
                and entry.get("text") == "전투 완료 btl_b1"
            ),
            {},
        )
        if (
            state.get("loaded") is not True
            or command_names.get("showPrototypeProgress") not in {"진행 기록 2", "진행 목표 2"}
            or command_names.get("startBattlePrototype") != "전투 완료 btl_b1"
            or len(completion_feedback_log) < 3
            or completion_feedback_entry.get("blockId") != "battle-complete:event-dialogue-block-015"
            or completion_feedback_entry.get("battleBackground") != "btl_b1"
            or completion_feedback_entry.get("durationMs") != 1100
            or completion_feedback_entry.get("battleCompletionSound") != "menuConfirm"
            or completion_feedback_entry.get("battleCompletionSoundSrc") != "../extract_wlk/04.wav"
            or completion_feedback_entry.get("battleCompletionSoundPlayed") is not True
            or completion_feedback_entry.get("browserBattleCompletionFeedbackImplemented") is not True
            or completion_feedback_entry.get("prototypeBattleCompletionImplemented") is not True
            or completion_feedback_render_entry.get("text") != "전투 완료 btl_b1"
            or completion_feedback_render_entry.get("battleCompletionSound") != "menuConfirm"
            or completion_feedback_render_entry.get("battleCompletionSoundSrc") != "../extract_wlk/04.wav"
            or completion_feedback_render_entry.get("battleCompletionSoundPlayed") is not True
            or completion_feedback_render_entry.get("browserBattleCompletionFeedbackImplemented") is not True
            or completion_feedback_last.get("text") != "전투 완료 btl_b1"
            or completion_feedback_last.get("battleCompletionSound") != "menuConfirm"
            or completion_feedback_last.get("battleCompletionSoundSrc") != "../extract_wlk/04.wav"
            or completion_feedback_last.get("battleCompletionSoundPlayed") is not True
            or completion_feedback_last_render.get("text") != "전투 완료 btl_b1"
            or completion_feedback_last_render.get("battleCompletionSound") != "menuConfirm"
            or completion_feedback_last_render.get("battleCompletionSoundSrc") != "../extract_wlk/04.wav"
            or completion_feedback_last_render.get("battleCompletionSoundPlayed") is not True
        ):
            raise WebDriverError(f"candidate battle completion restore state is incomplete: {state!r}")
        action_prompt = state.get("actionPrompt") or {}
        if (
            action_prompt.get("kind") != "battle-candidate"
            or action_prompt.get("text") != "Enter -> 전투 완료 btl_b1"
            or action_prompt.get("completed") is not True
            or action_prompt.get("id") != "event-dialogue-block-015"
            or action_prompt.get("blockId") != "event-dialogue-block-015"
            or action_prompt.get("battleBackground") != "btl_b1"
            or action_prompt.get("completedCount") != 1
            or action_prompt.get("originalEventDrivenBattleEntry") is not False
            or action_prompt.get("originalStoryFlagRuntimeImplemented") is not False
        ):
            raise WebDriverError(f"candidate battle completion action prompt is incomplete: {state!r}")
        objective_before = state.get("objectiveBefore") or {}
        objective_action = state.get("objectiveAction") or {}
        objective_notice = state.get("objectiveCompletionNotice") or {}
        objective_dialogue = state.get("objectiveActiveDialogueBlock") or {}
        objective_notice_lines = "\n".join(objective_notice.get("lines") or [])
        if (
            objective_before.get("title") != "후보 전투 완료 btl_b1"
            or objective_before.get("nextAction") != "완료 알림 확인"
            or "전투 승리 1" not in str(objective_before.get("detail") or "")
            or state.get("objectiveResult") is not True
            or objective_action.get("action") != "battle-completion-notice"
            or objective_action.get("handled") is not True
            or objective_action.get("activeId") != "battle-complete:event-dialogue-block-015"
            or objective_notice.get("blockId") != "battle-complete:event-dialogue-block-015"
            or "전투 보상 소지금 75 / 경험치 +7" not in objective_notice_lines
            or "전투 드롭 약초 2" not in objective_notice_lines
            or "전투 레벨 Ataho Lv2 3/125" not in objective_notice_lines
            or objective_dialogue.get("blockId") != "battle-complete:event-dialogue-block-015"
            or objective_dialogue.get("line") != "전투 완료 btl_b1"
        ):
            raise WebDriverError(f"candidate battle completion objective action is incomplete: {state!r}")
        notice = state.get("completionNotice") or {}
        dialogue = state.get("activeDialogueBlock") or {}
        notice_lines = "\n".join(notice.get("lines") or [])
        if (
            state.get("completedActionResult") is not True
            or state.get("scene") != "map"
            or notice.get("blockId") != "battle-complete:event-dialogue-block-015"
            or notice.get("map") != "map1_02b"
            or "전투 완료 btl_b1" not in notice_lines
            or "전투 보상 소지금 75 / 경험치 +7" not in notice_lines
            or "전투 드롭 약초 2" not in notice_lines
            or "전투 레벨 Ataho Lv2 3/125" not in notice_lines
            or (notice.get("completion") or {}).get("completed") is not True
            or notice.get("originalEventDrivenBattleEntry") is not False
            or notice.get("originalStoryFlagRuntimeImplemented") is not False
            or dialogue.get("blockId") != "battle-complete:event-dialogue-block-015"
            or dialogue.get("line") != "전투 완료 btl_b1"
            or dialogue.get("lineCount", 0) < 3
        ):
            raise WebDriverError(f"candidate battle completion notice did not block replay: {state!r}")
        menu_notice = state.get("menuCompletionNotice") or {}
        menu_dialogue = state.get("menuActiveDialogueBlock") or {}
        menu_notice_lines = "\n".join(menu_notice.get("lines") or [])
        if (
            state.get("menuBattleName") != "전투 완료 btl_b1"
            or state.get("menuBattleResult") is not True
            or menu_notice.get("blockId") != "battle-complete:event-dialogue-block-015"
            or menu_notice.get("map") != "map1_02b"
            or "전투 완료 btl_b1" not in menu_notice_lines
            or "전투 보상 소지금 75 / 경험치 +7" not in menu_notice_lines
            or "전투 드롭 약초 2" not in menu_notice_lines
            or "전투 레벨 Ataho Lv2 3/125" not in menu_notice_lines
            or (menu_notice.get("completion") or {}).get("completed") is not True
            or menu_notice.get("originalEventDrivenBattleEntry") is not False
            or menu_notice.get("originalStoryFlagRuntimeImplemented") is not False
            or menu_dialogue.get("blockId") != "battle-complete:event-dialogue-block-015"
            or menu_dialogue.get("line") != "전투 완료 btl_b1"
            or menu_dialogue.get("lineCount", 0) < 3
        ):
            raise WebDriverError(f"candidate battle completion menu command did not block replay: {state!r}")
    else:
        saved_progress = state.get("savedProgress") or {}
        saved_counts = saved_progress.get("counts") or {}
        auto_save = state.get("victoryAutoSave") or {}
        auto_save_counts = ((auto_save.get("progress") or {}).get("counts") or {})
        auto_save_tile = auto_save.get("payloadTile") or {}
        auto_save_completion = auto_save.get("completion") or {}
        saved_payload = state.get("savedPayload") or {}
        saved_story_flags = saved_payload.get("prototypeStoryFlags") or {}
        saved_tile = saved_payload.get("tile") or {}
        saved_route = saved_payload.get("routeState") or {}
        saved_field = saved_payload.get("fieldEncounter") or {}
        saved_runtime = saved_payload.get("runtimeState") or {}
        saved_runtime_herb = next((item for item in saved_runtime.get("items") or [] if item.get("key") == "herb"), {})
        summary = state.get("victorySummary") or {}
        detail = (summary.get("progressEvent") or {}).get("detail", {})
        reward_effect = state.get("rewardEffect") or {}
        reward_render = state.get("rewardEffectRender") or {}
        victory_sound = summary.get("victorySound") or {}
        auto_save_victory_sound = auto_save.get("victorySound") or {}
        reward_lines = " ".join(str(line) for line in (reward_effect.get("lines") or []))
        reward_render_lines = " ".join(str(line) for line in (reward_render.get("lines") or []))
        verify_battle_vm_replay(summary.get("vmReplay") or {}, "event-dialogue-block-015")
        verify_battle_vm_replay(detail.get("vmReplay") or {}, "event-dialogue-block-015")
        verify_battle_vm_replay(auto_save.get("vmReplay") or {}, "event-dialogue-block-015")
        items = (state.get("runtimeState") or {}).get("items") or []
        herb = next((item for item in items if item.get("key") == "herb"), {})
        if (
            state.get("saved") is not True
            or saved_counts.get("battle-victory") != 1
            or auto_save.get("saved") is not True
            or auto_save.get("source") != "battle-victory"
            or auto_save.get("map") != "map1_02b"
            or auto_save.get("payloadMap") != "map1_02b"
            or auto_save_tile.get("x") != 11
            or auto_save_tile.get("y") != 12
            or auto_save_counts.get("battle-start") != 1
            or auto_save_counts.get("battle-victory") != 1
            or auto_save_counts.get("field-encounter", 0) != 0
            or auto_save.get("runtimeMoney") != 75
            or next((item for item in auto_save.get("runtimeItems") or [] if item.get("key") == "herb"), {}).get("count") != 2
            or auto_save.get("battleId") != "event-dialogue-block-015"
            or auto_save.get("battleBackground") != "btl_b1"
            or auto_save.get("repeatableFieldEncounter") is not False
            or auto_save.get("originalEventDrivenBattleEntry") is not False
            or auto_save.get("originalStoryFlagRuntimeImplemented") is not False
            or battle_story_key not in story_flag_keys(auto_save.get("storyFlags") or {})
            or auto_save.get("storyFlagCount") != 1
            or battle_story_key not in set(auto_save.get("storyFlagKeys") or [])
            or (auto_save.get("storyFlagCounts") or {}).get("battle-victory") != 1
            or auto_save.get("storyFlagSource") != "prototype-progress-derived-story-flags"
            or auto_save.get("browserPrototypeStoryFlagImplemented") is not True
            or not sound_event_matches(auto_save_victory_sound, "victory", "/extract_wlk/12.wav")
            or auto_save_completion.get("id") != "event-dialogue-block-015"
            or auto_save_completion.get("completed") is not True
            or auto_save_completion.get("completedCount") != 1
            or auto_save_completion.get("remainingCount") != 0
            or saved_payload.get("map") != "map1_02b"
            or battle_story_key not in story_flag_keys(saved_story_flags)
            or saved_tile.get("x") != 11
            or saved_tile.get("y") != 12
            or saved_route.get("trialTransitions") != ""
            or saved_field.get("enabled") is not False
            or saved_runtime.get("money") != 75
            or saved_runtime_herb.get("name") != "약초"
            or saved_runtime_herb.get("count") != 2
            or summary.get("rewardGranted") is not True
            or detail.get("exp", 0) <= 0
            or not (detail.get("levelUps") or [])
            or detail.get("itemKey") != "herb"
            or detail.get("itemName") != "약초"
            or not item_text_provenance_matches(detail, "herb")
            or detail.get("dropCount") != 1
            or detail.get("dropCountBefore") != 1
            or detail.get("dropCountAfter") != 2
            or detail.get("dropCountGranted") != 1
            or detail.get("dropSource") != "prototype-enemy-drop"
            or detail.get("originalRewardTableMapped") is not False
            or detail.get("originalDropTableMapped") is not False
            or detail.get("originalLevelFormulaImplemented") is not False
            or not (summary.get("levelUps") or [])
            or (summary.get("itemDrop") or {}).get("itemKey") != "herb"
            or (summary.get("itemDrop") or {}).get("itemName") != "약초"
            or (summary.get("itemDrop") or {}).get("countAfter") != 2
            or not item_text_provenance_matches(summary.get("itemDrop") or {}, "herb")
            or not sound_event_matches(victory_sound, "victory", "/extract_wlk/12.wav")
            or not item_text_provenance_matches(auto_save, "herb")
            or reward_effect.get("source") != "battle-victory"
            or reward_effect.get("battleId") != "event-dialogue-block-015"
            or reward_effect.get("battleBackground") != "btl_b1"
            or reward_effect.get("money") != detail.get("money")
            or reward_effect.get("exp") != detail.get("exp")
            or (reward_effect.get("itemDrop") or {}).get("itemName") != "약초"
            or (reward_effect.get("itemDrop") or {}).get("countGranted") != 1
            or not item_text_provenance_matches(reward_effect.get("itemDrop") or {}, "herb")
            or not (reward_effect.get("levelUps") or [])
            or reward_effect.get("durationMs") != 1400
            or reward_effect.get("battleRewardSound") != "victory"
            or not str(reward_effect.get("battleRewardSoundSrc") or "").endswith("/extract_wlk/12.wav")
            or reward_effect.get("battleRewardSoundPlayed") is not True
            or reward_effect.get("browserBattleRewardFeedbackImplemented") is not True
            or reward_effect.get("originalRewardTableMapped") is not False
            or reward_effect.get("originalDropTableMapped") is not False
            or reward_effect.get("originalLevelFormulaImplemented") is not False
            or "경험치 +7" not in reward_lines
            or "약초 +1" not in reward_lines
            or "Ataho Lv2" not in reward_lines
            or reward_render.get("source") != "battle-victory"
            or reward_render.get("battleId") != "event-dialogue-block-015"
            or reward_render.get("battleBackground") != "btl_b1"
            or reward_render.get("money") != detail.get("money")
            or reward_render.get("exp") != detail.get("exp")
            or reward_render.get("itemName") != "약초"
            or reward_render.get("itemCountGranted") != 1
            or not item_text_provenance_matches(reward_render, "herb")
            or reward_render.get("levelUpCount", 0) <= 0
            or reward_render.get("durationMs") != 1400
            or reward_render.get("battleRewardSound") != "victory"
            or not str(reward_render.get("battleRewardSoundSrc") or "").endswith("/extract_wlk/12.wav")
            or reward_render.get("battleRewardSoundPlayed") is not True
            or reward_render.get("active") is not True
            or reward_render.get("browserBattleRewardFeedbackImplemented") is not True
            or "경험치 +7" not in reward_render_lines
            or "약초 +1" not in reward_render_lines
            or "Ataho Lv2" not in reward_render_lines
            or (state.get("runtimeState") or {}).get("money", 0) <= 0
            or (state.get("runtimeState") or {}).get("expTotal", 0) <= 0
            or herb.get("name") != "약초"
            or herb.get("count") != 2
        ):
            raise WebDriverError(f"candidate battle completion save state is incomplete: {state!r}")
        verify_story_flags(
            saved_story_flags,
            {battle_story_key},
            {"battle-victory": 1},
            "candidate battle completion saved payload",
        )
        verify_story_flags(
            auto_save.get("storyFlags") or {},
            {battle_story_key},
            {"battle-victory": 1},
            "candidate battle completion victory marker",
        )
        leveled = ((state.get("runtimeState") or {}).get("characterExp") or [{}])[0]
        if (
            leveled.get("name") != "Ataho"
            or leveled.get("level", 0) < 2
            or leveled.get("exp") != 3
            or leveled.get("expMax", 0) <= 100
            or leveled.get("hpMax", 0) <= 36
            or leveled.get("mpMax", 0) <= 8
            or leveled.get("atk", 0) <= 0
            or leveled.get("def", 0) <= 0
        ):
            raise WebDriverError(f"candidate battle level-up state is incomplete: {state!r}")


def verify_battle_completion_title_continue_state(title_state: dict, click_state: dict, restored_state: dict) -> None:
    title_labels = [str(label) for label in (title_state.get("titleMenuLabels") or [])]
    click_labels = [str(label) for label in (click_state.get("titleMenuLabels") or [])]
    verify_battle_completion_state(restored_state, restored=True)
    runtime = restored_state.get("runtimeState") or {}
    items = runtime.get("items") or []
    herb = next((item for item in items if item.get("key") == "herb"), {})
    leveled = (runtime.get("characterExp") or [{}])[0]
    if (
        title_state.get("scene") != "title"
        or title_state.get("quickLoadHidden") is not False
        or title_state.get("quickLoadText") != "이어하기"
        or "continue" not in (title_state.get("titleMenuKeys") or [])
        or not any("이어하기 map1_02b 11,12" in label for label in title_labels)
        or not isinstance(click_state, dict)
        or click_state.get("ok") is not True
        or click_state.get("text") != "이어하기"
        or not any("이어하기 map1_02b 11,12" in label for label in click_labels)
        or restored_state.get("titleContinue") is not True
        or "map=map1_02b" not in str(restored_state.get("search") or "")
        or "startTile=11%2C12" not in str(restored_state.get("search") or "")
        or restored_state.get("quickLoadHidden") is not False
        or restored_state.get("quickLoadText") != "임시 불러오기"
        or runtime.get("money") != 75
        or runtime.get("expTotal") != 3
        or herb.get("name") != "약초"
        or herb.get("count") != 2
        or leveled.get("level") != 2
        or leveled.get("exp") != 3
        or leveled.get("expMax") != 125
    ):
        raise WebDriverError(
            "candidate battle title continue did not restore completion/reward state: "
            f"title={title_state!r} click={click_state!r} restored={restored_state!r}"
        )


def verify_battle_outcome_feedback_state(
    state: dict,
    *,
    kind: str,
    expected_text: str,
    expected_message: str,
    expected_sound: str,
    expected_sound_src: str,
) -> None:
    log = state.get("outcomeFeedbackLog") or []
    rendered = state.get("outcomeFeedbackRender") or []
    last = state.get("outcomeFeedbackLast") or {}
    last_render = state.get("outcomeFeedbackLastRender") or {}
    named_feedback = state.get("runFeedback") if kind == "battle-run" else state.get("defeatFeedback")
    named_feedback = named_feedback or {}
    auto_feedback = ((state.get("autoSave") or {}).get("outcomeFeedback") or {})
    if (
        len(log) != 1
        or not rendered
        or log[0].get("source") != "battle-outcome-feedback"
        or log[0].get("kind") != kind
        or log[0].get("text") != expected_text
        or log[0].get("message") != expected_message
        or log[0].get("scene") != "map"
        or log[0].get("map") != "map1_02b"
        or log[0].get("battleId") != "event-dialogue-block-015"
        or log[0].get("battleBackground") != "btl_b1"
        or log[0].get("battleOutcomeSound") != expected_sound
        or log[0].get("battleOutcomeSoundSrc") != expected_sound_src
        or log[0].get("battleOutcomeSoundPlayed") is not True
        or log[0].get("durationMs") != 1100
        or log[0].get("browserBattleOutcomeFeedbackImplemented") is not True
        or log[0].get("prototypeBattleOutcomeImplemented") is not True
        or log[0].get("originalEventDrivenBattleEntry") is not False
        or log[0].get("originalRewardTableMapped") is not False
        or log[0].get("originalStoryFlagRuntimeImplemented") is not False
        or last.get("source") != "battle-outcome-feedback"
        or last.get("kind") != kind
        or last.get("text") != expected_text
        or last.get("battleOutcomeSound") != expected_sound
        or last.get("battleOutcomeSoundSrc") != expected_sound_src
        or last.get("battleOutcomeSoundPlayed") is not True
        or last_render.get("source") != "battle-outcome-feedback"
        or last_render.get("kind") != kind
        or last_render.get("text") != expected_text
        or last_render.get("battleOutcomeSound") != expected_sound
        or last_render.get("battleOutcomeSoundSrc") != expected_sound_src
        or last_render.get("battleOutcomeSoundPlayed") is not True
        or last_render.get("durationMs") != 1100
        or last_render.get("browserBattleOutcomeFeedbackImplemented") is not True
        or named_feedback.get("source") != "battle-outcome-feedback"
        or named_feedback.get("kind") != kind
        or named_feedback.get("text") != expected_text
        or named_feedback.get("battleOutcomeSound") != expected_sound
        or named_feedback.get("battleOutcomeSoundSrc") != expected_sound_src
        or named_feedback.get("battleOutcomeSoundPlayed") is not True
        or auto_feedback.get("source") != "battle-outcome-feedback"
        or auto_feedback.get("kind") != kind
        or auto_feedback.get("text") != expected_text
        or auto_feedback.get("battleOutcomeSound") != expected_sound
        or auto_feedback.get("battleOutcomeSoundSrc") != expected_sound_src
        or auto_feedback.get("battleOutcomeSoundPlayed") is not True
        or auto_feedback.get("durationMs") != 1100
        or auto_feedback.get("browserBattleOutcomeFeedbackImplemented") is not True
    ):
        raise WebDriverError(f"candidate battle {kind} did not render/save outcome feedback: {state!r}")
    if kind == "battle-defeat":
        if (
            log[0].get("recoveredName") != "Ataho"
            or log[0].get("recoveredHp") != 1
            or last_render.get("recoveredName") != "Ataho"
            or last_render.get("recoveredHp") != 1
            or auto_feedback.get("recoveredName") != "Ataho"
            or auto_feedback.get("recoveredHp") != 1
        ):
            raise WebDriverError(f"candidate battle defeat feedback did not preserve recovery marker: {state!r}")


def verify_battle_outcome_completion_notice_feedback(state: dict, kind: str) -> None:
    label = "패배" if kind == "battle-defeat" else "도주"
    expected_text = f"전투 {label} 완료 1/1"
    notice = state.get("objectiveCompletionNotice") or {}
    notice_feedback = notice.get("feedback") or {}
    feedback_log = state.get("outcomeNoticeFeedbackLog") or []
    feedback_render_log = state.get("outcomeNoticeFeedbackRender") or []
    feedback = state.get("outcomeNoticeFeedbackLast") or {}
    feedback_render = state.get("outcomeNoticeFeedbackLastRender") or {}
    if (
        notice_feedback.get("source") != "battle-outcome-completion-notice-feedback"
        or notice_feedback.get("kind") != kind
        or notice_feedback.get("text") != expected_text
        or notice_feedback.get("battleOutcomeSound") != "menuConfirm"
        or notice_feedback.get("battleOutcomeSoundSrc") != "../extract_wlk/04.wav"
        or notice_feedback.get("battleOutcomeSoundPlayed") is not True
        or notice_feedback.get("browserBattleOutcomeFeedbackImplemented") is not True
        or notice_feedback.get("battleOutcomeCompletionNoticeImplemented") is not True
        or not feedback_log
        or not feedback_render_log
        or feedback.get("source") != "battle-outcome-completion-notice-feedback"
        or feedback.get("kind") != kind
        or feedback.get("text") != expected_text
        or feedback.get("battleOutcomeSound") != "menuConfirm"
        or feedback.get("battleOutcomeSoundSrc") != "../extract_wlk/04.wav"
        or feedback.get("battleOutcomeSoundPlayed") is not True
        or feedback.get("browserBattleOutcomeFeedbackImplemented") is not True
        or feedback.get("battleOutcomeCompletionNoticeImplemented") is not True
        or feedback_render.get("source") != "battle-outcome-completion-notice-feedback"
        or feedback_render.get("kind") != kind
        or feedback_render.get("text") != expected_text
        or feedback_render.get("battleOutcomeSound") != "menuConfirm"
        or feedback_render.get("battleOutcomeSoundSrc") != "../extract_wlk/04.wav"
        or feedback_render.get("battleOutcomeSoundPlayed") is not True
        or feedback_render.get("browserBattleOutcomeFeedbackImplemented") is not True
        or feedback_render.get("battleOutcomeCompletionNoticeImplemented") is not True
    ):
        raise WebDriverError(f"candidate battle {kind} completion notice feedback is incomplete: {state!r}")


def verify_battle_run_state(state: dict) -> None:
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    record = state.get("progressRecord") or {}
    record_detail = record.get("detail") or {}
    completion = state.get("completion") or {}
    battle = completion.get("battle") or {}
    battle_outcome = completion.get("battleOutcome") or {}
    objective_before = state.get("objectiveBefore") or {}
    objective_action = state.get("objectiveAction") or {}
    objective_notice = state.get("objectiveCompletionNotice") or {}
    objective_dialogue = state.get("objectiveActiveDialogueBlock") or {}
    objective_notice_lines = "\n".join(objective_notice.get("lines") or [])
    runtime_state = state.get("runtimeState") or {}
    auto_save = state.get("autoSave") or {}
    auto_save_counts = ((auto_save.get("progress") or {}).get("counts") or {})
    saved_payload = state.get("savedPayload") or {}
    saved_counts = ((saved_payload.get("prototypeProgress") or {}).get("counts") or {})
    verify_battle_command_text_provenance(state.get("runCommand") or {}, 0)
    verify_battle_command_text_provenance(record_detail, 0, prefix="command")
    verify_battle_command_text_provenance(auto_save, 0, prefix="command")
    verify_battle_outcome_feedback_state(
        state,
        kind="battle-run",
        expected_text="전투 도주 btl_b1",
        expected_message="도주했다.",
        expected_sound="transition",
        expected_sound_src="../extract_wlk/07.wav",
    )
    verify_battle_outcome_completion_notice_feedback(state, "battle-run")
    if (
        state.get("result") is not True
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or state.get("notice") != "도주했다."
        or state.get("battleStateActive") is not False
        or counts.get("battle-start") != 1
        or counts.get("battle-run") != 1
        or counts.get("battle-victory", 0) != 0
        or counts.get("battle-defeat", 0) != 0
        or record.get("kind") != "battle-run"
        or record.get("id") != "event-dialogue-block-015"
        or record_detail.get("command") != "도주"
        or record_detail.get("originalEventDrivenBattleEntry") is not False
        or record_detail.get("originalStoryFlagRuntimeImplemented") is not False
        or battle.get("id") != "event-dialogue-block-015"
        or battle.get("completed") is not False
        or battle.get("completedCount") != 0
        or battle.get("remainingCount") != 1
        or battle.get("originalEventDrivenBattleEntry") is not False
        or battle.get("originalStoryFlagRuntimeImplemented") is not False
        or battle_outcome.get("id") != "event-dialogue-block-015"
        or battle_outcome.get("kind") != "battle-run"
        or battle_outcome.get("label") != "도주"
        or battle_outcome.get("battleBackground") != "btl_b1"
        or battle_outcome.get("completed") is not True
        or battle_outcome.get("completedCount") != 1
        or battle_outcome.get("rewardGranted") is not False
        or battle_outcome.get("originalEventDrivenBattleEntry") is not False
        or battle_outcome.get("originalRewardTableMapped") is not False
        or battle_outcome.get("originalStoryFlagRuntimeImplemented") is not False
        or objective_before.get("title") != "후보 전투 도주 btl_b1"
        or objective_before.get("nextAction") != "완료 알림 확인"
        or objective_before.get("source") != "prototype-battle-outcome-completion"
        or state.get("objectiveResult") is not True
        or objective_action.get("action") != "battle-outcome-completion-notice"
        or objective_action.get("activeId") != "battle-outcome-complete:map1_02b:battle-run"
        or objective_action.get("rewardGranted") is not False
        or objective_notice.get("blockId") != "battle-outcome-complete:map1_02b:battle-run"
        or "전투 도주 완료 1/1" not in objective_notice_lines
        or "btl_b1" not in objective_notice_lines
        or "도주 결과 보상 없음 / 소지금 60 / 경험치 0" not in objective_notice_lines
        or "원본 reward/story flag 실행 증명은 아직 아닙니다." not in objective_notice_lines
        or objective_dialogue.get("blockId") != "battle-outcome-complete:map1_02b:battle-run"
        or completion.get("originalStoryFlagRuntimeImplemented") is not False
        or progress.get("originalStoryFlagRuntimeImplemented") is not False
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "battle-run"
        or auto_save.get("payloadMap") != "map1_02b"
        or auto_save_counts.get("battle-run") != 1
        or auto_save_counts.get("battle-victory", 0) != 0
        or saved_payload.get("map") != "map1_02b"
        or saved_counts.get("battle-run") != 1
        or saved_counts.get("battle-victory", 0) != 0
        or runtime_state.get("money") != 60
        or runtime_state.get("expTotal") != 0
        or (state.get("summary") or {}).get("rewardGranted") is True
    ):
        raise WebDriverError(f"candidate battle run state should not grant victory/reward/completion: {state!r}")


def verify_battle_defeat_state(state: dict) -> None:
    before = state.get("beforeClose") or {}
    progress = state.get("progress") or {}
    before_progress = before.get("progress") or {}
    counts = progress.get("counts") or {}
    before_counts = before_progress.get("counts") or {}
    record = state.get("progressRecord") or {}
    before_record = before.get("progressRecord") or {}
    before_record_detail = before_record.get("detail") or {}
    before_turn = before.get("turnMarker") or {}
    before_turn_auto = before.get("turnAutoSave") or {}
    defend_feedback = next(
        (
            entry
            for entry in (before.get("defendFeedbackLog") or [])
            if isinstance(entry, dict)
            and entry.get("source") == "battle-defend-feedback"
        ),
        {},
    )
    defend_feedback_render = next(
        (
            entry
            for entry in (before.get("defendFeedbackRender") or [])
            if isinstance(entry, dict)
            and entry.get("source") == "battle-defend-feedback"
        ),
        {},
    )
    completion = state.get("completion") or {}
    battle = completion.get("battle") or {}
    battle_outcome = completion.get("battleOutcome") or {}
    objective_before = state.get("objectiveBefore") or {}
    objective_action = state.get("objectiveAction") or {}
    objective_notice = state.get("objectiveCompletionNotice") or {}
    objective_dialogue = state.get("objectiveActiveDialogueBlock") or {}
    objective_notice_lines = "\n".join(objective_notice.get("lines") or [])
    before_runtime_state = before.get("runtimeState") or {}
    runtime_state = state.get("runtimeState") or {}
    recovery = state.get("defeatRecovery") or {}
    auto_save = state.get("autoSave") or {}
    auto_save_counts = ((auto_save.get("progress") or {}).get("counts") or {})
    saved_payload = state.get("savedPayload") or {}
    saved_counts = ((saved_payload.get("prototypeProgress") or {}).get("counts") or {})
    saved_runtime = saved_payload.get("runtimeState") or {}
    saved_party_hp = [
        {"name": row.get("name"), "hp": row.get("hp"), "hpMax": row.get("hpMax")}
        for row in (saved_runtime.get("characters") or [])
    ]
    saved_ataho = next((row for row in saved_party_hp if row.get("name") == "Ataho"), {})
    party_hp_after = runtime_state.get("partyHp") or []
    ataho_after = next((row for row in party_hp_after if row.get("name") == "Ataho"), {})
    verify_battle_command_text_provenance(before.get("defendCommand") or {}, 1)
    verify_battle_command_text_provenance(before_record_detail, 1, prefix="command")
    verify_battle_command_text_provenance(before_turn, 1, prefix="command")
    verify_battle_command_text_provenance(before_turn_auto, 1, prefix="command")
    verify_battle_command_text_provenance(auto_save, 1, prefix="command")
    verify_battle_outcome_feedback_state(
        state,
        kind="battle-defeat",
        expected_text="전투 패배 btl_b1",
        expected_message="패배했다.",
        expected_sound="battleHit",
        expected_sound_src="../extract_wlk/09.wav",
    )
    verify_battle_outcome_completion_notice_feedback(state, "battle-defeat")
    if (
        state.get("result") is not True
        or state.get("closeResult") is not True
        or before.get("scene") != "battle"
        or before.get("finished") is not True
        or before.get("finishMessage") != "패배했다."
        or before.get("rewardGranted") is not False
        or "패배했다." not in [str(line) for line in before.get("log") or []]
        or before_counts.get("battle-start") != 1
        or before_counts.get("battle-defeat") != 1
        or before_counts.get("battle-victory", 0) != 0
        or before_record.get("kind") != "battle-defeat"
        or before_record.get("id") != "event-dialogue-block-015"
        or before_record_detail.get("reason") != "party-defeated"
        or before_record_detail.get("command") != "방어"
        or before_record_detail.get("originalEventDrivenBattleEntry") is not False
        or before_record_detail.get("originalStoryFlagRuntimeImplemented") is not False
        or before_turn.get("source") != "prototype-battle-turn-result"
        or before_turn.get("commandName") != "방어"
        or before_turn.get("commandKey") != "defend"
        or before_turn.get("guarded") is not True
        or (before_turn.get("defendFeedback") or {}).get("source") != "battle-defend-feedback"
        or (before_turn.get("defendFeedback") or {}).get("battleDefendSound") != "battleHit"
        or (before_turn.get("defendFeedback") or {}).get("battleDefendSoundSrc") != "../extract_wlk/09.wav"
        or (before_turn.get("defendFeedback") or {}).get("battleDefendSoundPlayed") is not True
        or before_turn_auto.get("saved") is not True
        or before_turn_auto.get("source") != "battle-turn-prototype"
        or before_turn_auto.get("scope") != "battle-turn"
        or before_turn_auto.get("commandName") != "방어"
        or (before_turn_auto.get("defendFeedback") or {}).get("source") != "battle-defend-feedback"
        or (before_turn_auto.get("defendFeedback") or {}).get("battleDefendSound") != "battleHit"
        or (before_turn_auto.get("defendFeedback") or {}).get("battleDefendSoundSrc") != "../extract_wlk/09.wav"
        or (before_turn_auto.get("defendFeedback") or {}).get("battleDefendSoundPlayed") is not True
        or defend_feedback.get("text") != "방어 피해 2"
        or defend_feedback.get("actorName") != "Ataho"
        or defend_feedback.get("commandName") != "방어"
        or defend_feedback.get("commandKey") != "defend"
        or defend_feedback.get("targetName") != "Ataho"
        or defend_feedback.get("enemyName") != "푸른 마수"
        or defend_feedback.get("enemyActionName") != "할퀴기"
        or defend_feedback.get("hpBefore") != 1
        or defend_feedback.get("hpAfter") != 0
        or defend_feedback.get("damage") != 2
        or defend_feedback.get("unguardedDamage") != 4
        or defend_feedback.get("guarded") is not True
        or defend_feedback.get("battleDefendSound") != "battleHit"
        or defend_feedback.get("battleDefendSoundSrc") != "../extract_wlk/09.wav"
        or defend_feedback.get("battleDefendSoundPlayed") is not True
        or defend_feedback.get("durationMs") != 900
        or defend_feedback.get("browserBattleDefendFeedbackImplemented") is not True
        or defend_feedback.get("originalCombatFormulaImplemented") is not False
        or defend_feedback.get("originalStoryFlagRuntimeImplemented") is not False
        or defend_feedback_render.get("text") != "방어 피해 2"
        or defend_feedback_render.get("battleDefendSound") != "battleHit"
        or defend_feedback_render.get("battleDefendSoundSrc") != "../extract_wlk/09.wav"
        or defend_feedback_render.get("battleDefendSoundPlayed") is not True
        or defend_feedback_render.get("active") is not True
        or defend_feedback_render.get("browserBattleDefendFeedbackImplemented") is not True
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or state.get("notice") != "패배했다."
        or state.get("battleStateActive") is not False
        or counts.get("battle-start") != 1
        or counts.get("battle-defeat") != 1
        or counts.get("battle-victory", 0) != 0
        or counts.get("battle-run", 0) != 0
        or record.get("kind") != "battle-defeat"
        or battle.get("id") != "event-dialogue-block-015"
        or battle.get("completed") is not False
        or battle.get("completedCount") != 0
        or battle.get("remainingCount") != 1
        or battle.get("originalEventDrivenBattleEntry") is not False
        or battle.get("originalStoryFlagRuntimeImplemented") is not False
        or battle_outcome.get("id") != "event-dialogue-block-015"
        or battle_outcome.get("kind") != "battle-defeat"
        or battle_outcome.get("label") != "패배"
        or battle_outcome.get("battleBackground") != "btl_b1"
        or battle_outcome.get("completed") is not True
        or battle_outcome.get("completedCount") != 1
        or battle_outcome.get("rewardGranted") is not False
        or battle_outcome.get("recoveredName") != "Ataho"
        or battle_outcome.get("recoveredHp") != 1
        or battle_outcome.get("originalEventDrivenBattleEntry") is not False
        or battle_outcome.get("originalRewardTableMapped") is not False
        or battle_outcome.get("originalStoryFlagRuntimeImplemented") is not False
        or objective_before.get("title") != "후보 전투 패배 btl_b1"
        or objective_before.get("nextAction") != "완료 알림 확인"
        or objective_before.get("source") != "prototype-battle-outcome-completion"
        or state.get("objectiveResult") is not True
        or objective_action.get("action") != "battle-outcome-completion-notice"
        or objective_action.get("activeId") != "battle-outcome-complete:map1_02b:battle-defeat"
        or objective_action.get("rewardGranted") is not False
        or objective_notice.get("blockId") != "battle-outcome-complete:map1_02b:battle-defeat"
        or "전투 패배 완료 1/1" not in objective_notice_lines
        or "btl_b1" not in objective_notice_lines
        or "패배 결과 보상 없음 / 소지금 60 / 경험치 0" not in objective_notice_lines
        or "패배 복구 Ataho HP 1" not in objective_notice_lines
        or "원본 reward/story flag 실행 증명은 아직 아닙니다." not in objective_notice_lines
        or objective_dialogue.get("blockId") != "battle-outcome-complete:map1_02b:battle-defeat"
        or completion.get("originalStoryFlagRuntimeImplemented") is not False
        or progress.get("originalStoryFlagRuntimeImplemented") is not False
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "battle-defeat"
        or auto_save.get("payloadMap") != "map1_02b"
        or auto_save_counts.get("battle-defeat") != 1
        or auto_save_counts.get("battle-victory", 0) != 0
        or (auto_save.get("defeatRecovery") or {}).get("recovered") is not True
        or saved_payload.get("map") != "map1_02b"
        or saved_counts.get("battle-defeat") != 1
        or saved_counts.get("battle-victory", 0) != 0
        or saved_runtime.get("money") != 60
        or saved_ataho.get("hp") != 1
        or before_runtime_state.get("money") != 60
        or before_runtime_state.get("expTotal") != 0
        or runtime_state.get("money") != 60
        or runtime_state.get("expTotal") != 0
        or recovery.get("source") != "prototype-battle-defeat-recovery"
        or recovery.get("recovered") is not True
        or recovery.get("target") != "Ataho"
        or recovery.get("hpBefore") != 0
        or recovery.get("hpAfter") != 1
        or recovery.get("livingBefore") != 0
        or recovery.get("livingAfter") != 1
        or recovery.get("originalStoryFlagRuntimeImplemented") is not False
        or ataho_after.get("hp") != 1
    ):
        raise WebDriverError(f"candidate battle defeat state should not grant victory/reward/completion: {state!r}")


def verify_battle_nonvictory_title_continue_state(
    kind: str,
    title_state: dict,
    click_state: dict,
    restored_state: dict,
) -> None:
    title_labels = [str(label) for label in (title_state.get("titleMenuLabels") or [])]
    click_labels = [str(label) for label in (click_state.get("titleMenuLabels") or [])]
    progress = restored_state.get("progress") or {}
    counts = progress.get("counts") or {}
    battle = ((restored_state.get("completion") or {}).get("battle") or {})
    battle_outcome = ((restored_state.get("completion") or {}).get("battleOutcome") or {})
    objective_before = restored_state.get("objectiveBefore") or {}
    objective_action = restored_state.get("objectiveAction") or {}
    objective_notice = restored_state.get("objectiveCompletionNotice") or {}
    objective_dialogue = restored_state.get("objectiveActiveDialogueBlock") or {}
    objective_notice_lines = "\n".join(objective_notice.get("lines") or [])
    runtime = restored_state.get("runtimeState") or {}
    field = restored_state.get("fieldEncounter") or {}
    party_hp = runtime.get("partyHp") or []
    ataho = next((row for row in party_hp if row.get("name") == "Ataho"), {})
    verify_battle_outcome_completion_notice_feedback(restored_state, kind)
    if (
        title_state.get("scene") != "title"
        or title_state.get("quickLoadHidden") is not False
        or title_state.get("quickLoadText") != "이어하기"
        or "continue" not in (title_state.get("titleMenuKeys") or [])
        or not any("이어하기 map1_02b 11,12" in label for label in title_labels)
        or not isinstance(click_state, dict)
        or click_state.get("ok") is not True
        or click_state.get("text") != "이어하기"
        or not any("이어하기 map1_02b 11,12" in label for label in click_labels)
        or restored_state.get("titleContinue") is not True
        or restored_state.get("expectedKind") != kind
        or restored_state.get("scene") != "map"
        or restored_state.get("map") != "map1_02b"
        or "map=map1_02b" not in str(restored_state.get("search") or "")
        or "startTile=11%2C12" not in str(restored_state.get("search") or "")
        or counts.get(kind) != 1
        or counts.get("battle-victory", 0) != 0
        or battle.get("id") != "event-dialogue-block-015"
        or battle.get("completed") is not False
        or battle.get("completedCount") != 0
        or battle.get("remainingCount") != 1
        or battle_outcome.get("id") != "event-dialogue-block-015"
        or battle_outcome.get("kind") != kind
        or battle_outcome.get("label") != ("패배" if kind == "battle-defeat" else "도주")
        or battle_outcome.get("battleBackground") != "btl_b1"
        or battle_outcome.get("completed") is not True
        or battle_outcome.get("rewardGranted") is not False
        or battle_outcome.get("originalRewardTableMapped") is not False
        or battle_outcome.get("originalStoryFlagRuntimeImplemented") is not False
        or objective_before.get("title") != (
            "후보 전투 패배 btl_b1" if kind == "battle-defeat" else "후보 전투 도주 btl_b1"
        )
        or objective_before.get("nextAction") != "완료 알림 확인"
        or objective_before.get("source") != "prototype-battle-outcome-completion"
        or restored_state.get("objectiveResult") is not True
        or objective_action.get("action") != "battle-outcome-completion-notice"
        or objective_action.get("activeId") != f"battle-outcome-complete:map1_02b:{kind}"
        or objective_notice.get("blockId") != f"battle-outcome-complete:map1_02b:{kind}"
        or (
            "전투 패배 완료 1/1" if kind == "battle-defeat" else "전투 도주 완료 1/1"
        ) not in objective_notice_lines
        or "btl_b1" not in objective_notice_lines
        or (
            "패배 결과 보상 없음 / 소지금 60 / 경험치 0"
            if kind == "battle-defeat"
            else "도주 결과 보상 없음 / 소지금 60 / 경험치 0"
        ) not in objective_notice_lines
        or objective_dialogue.get("blockId") != f"battle-outcome-complete:map1_02b:{kind}"
        or restored_state.get("buttonText") != "전투 btl_b1"
        or restored_state.get("quickLoadHidden") is not False
        or restored_state.get("quickLoadText") != "임시 불러오기"
        or runtime.get("money") != 60
        or runtime.get("expTotal") != 0
        or field.get("enabled") is not False
        or restored_state.get("loadedSaveSummary") is not None
    ):
        raise WebDriverError(
            f"candidate battle {kind} title continue did not restore non-victory state: "
            f"title={title_state!r} click={click_state!r} restored={restored_state!r}"
        )
    if kind == "battle-defeat" and ataho.get("hp") != 1:
        raise WebDriverError(
            "candidate battle defeat title continue did not preserve recovered Ataho HP 1: "
            f"title={title_state!r} click={click_state!r} restored={restored_state!r}"
        )
    if kind == "battle-defeat" and (
        battle_outcome.get("recoveredName") != "Ataho"
        or battle_outcome.get("recoveredHp") != 1
        or "패배 복구 Ataho HP 1" not in objective_notice_lines
    ):
        raise WebDriverError(
            "candidate battle defeat title continue did not expose the defeat recovery completion notice: "
            f"title={title_state!r} click={click_state!r} restored={restored_state!r}"
        )


def verify_battle_status_cure_state(state: dict) -> None:
    summary = state.get("summary") or {}
    action = state.get("enemyAction") or {}
    no_target_failure = state.get("noTargetFailure") or {}
    target_failure_before = no_target_failure.get("before") or {}
    target_failure_marker = no_target_failure.get("failure") or {}
    target_failure_progress = no_target_failure.get("progress") or {}
    target_failure_counts = target_failure_progress.get("counts") or {}
    target_failure_sound_counts = ((no_target_failure.get("soundState") or {}).get("counts") or {})
    no_status_failure = state.get("noStatusFailure") or {}
    failure_before = no_status_failure.get("before") or {}
    failure_after = no_status_failure.get("after") or {}
    failure_marker = no_status_failure.get("failure") or {}
    failure_progress = no_status_failure.get("progress") or {}
    failure_counts = failure_progress.get("counts") or {}
    failure_sound_counts = ((no_status_failure.get("soundState") or {}).get("counts") or {})
    after_poison = state.get("afterPoison") or {}
    after_cure = state.get("afterCure") or {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    completion = state.get("completion") or {}
    battle = completion.get("battle") or {}
    log_text = " ".join(str(line) for line in state.get("log") or [])
    item_use = state.get("itemUse") or {}
    item_progress_detail = ((item_use.get("progressEvent") or {}).get("detail") or {})
    item_feedback = battle_item_feedback_match(
        state,
        source="battle-item-use",
        item_key="item_2",
        text="독 회복",
    )
    item_feedback_render = battle_item_feedback_match(
        state,
        source="battle-item-use",
        item_key="item_2",
        text="독 회복",
        rendered=True,
    )
    target_failure_feedback = battle_item_feedback_match(
        no_target_failure,
        source="battle-item-failed-feedback",
        item_key="herb",
        text="약초 대상 없음",
    )
    target_failure_feedback_render = battle_item_feedback_match(
        no_target_failure,
        source="battle-item-failed-feedback",
        item_key="herb",
        text="약초 대상 없음",
        rendered=True,
    )
    failure_feedback = battle_item_feedback_match(
        no_status_failure,
        source="battle-item-failed-feedback",
        item_key="item_2",
        text="해독초 회복 대상 없음",
    )
    failure_feedback_render = battle_item_feedback_match(
        no_status_failure,
        source="battle-item-failed-feedback",
        item_key="item_2",
        text="해독초 회복 대상 없음",
        rendered=True,
    )
    action_text = expected_battle_action_text_table(12)
    if (
        state.get("scene") != "battle"
        or state.get("map") != "map2_07e"
        or summary.get("candidateId") != "event-dialogue-block-038:btl_n2"
        or summary.get("battleBackground") != "btl_n2"
        or summary.get("enemyName") != "철갑 코뿔"
        or summary.get("enemyActionName") != "독액"
        or summary.get("enemyActionSource") != "prototype-enemy-action"
        or summary.get("enemyActionNameSource") != "exe-text-table-battleCommands"
        or summary.get("enemyActionTextTableKey") != action_text["key"]
        or summary.get("enemyActionTextTableIndex") != action_text["index"]
        or summary.get("enemyActionTextTableRefVaHex") != action_text["refVaHex"]
        or summary.get("enemyActionTextTableTextVaHex") != action_text["textVaHex"]
        or summary.get("enemyStatusKey") != "poison"
        or summary.get("enemyStatusName") != "독"
        or summary.get("enemyDropName") != "리프레시 워터"
        or summary.get("originalEnemyAiImplemented") is not False
        or summary.get("originalStatusFormulaImplemented") is not False
        or action.get("name") != "독액"
        or action.get("statusKey") != "poison"
        or action.get("statusName") != "독"
        or action.get("source") != "prototype-enemy-action"
        or action.get("nameSource") != "exe-text-table-battleCommands"
        or action.get("textTableKey") != action_text["key"]
        or action.get("textTableIndex") != action_text["index"]
        or action.get("textTableRefVaHex") != action_text["refVaHex"]
        or action.get("textTableTextVaHex") != action_text["textVaHex"]
        or action.get("originalEnemyAiImplemented") is not False
        or action.get("originalStatusFormulaImplemented") is not False
        or no_target_failure.get("scene") != "battle"
        or no_target_failure.get("map") != "map2_07e"
        or target_failure_before.get("name") != "Ataho"
        or target_failure_before.get("hp") != target_failure_before.get("hpMax")
        or target_failure_before.get("statuses") != []
        or target_failure_before.get("sourceStatuses") != []
        or no_target_failure.get("itemResult") is not True
        or (no_target_failure.get("itemCommand") or {}).get("key") != "herb"
        or (no_target_failure.get("itemCommand") or {}).get("name") != "약초"
        or no_target_failure.get("itemCountBefore") != 1
        or no_target_failure.get("itemCountAfter") != 1
        or no_target_failure.get("itemUse") is not None
        or no_target_failure.get("itemUseAutoSave") is not None
        or target_failure_counts.get("item-use-prototype")
        or int(target_failure_sound_counts.get("item") or 0) != 0
        or target_failure_marker.get("source") != "prototype-battle-item-effect"
        or target_failure_marker.get("failed") is not True
        or target_failure_marker.get("failureReason") != "no-target"
        or target_failure_marker.get("itemKey") != "herb"
        or target_failure_marker.get("itemName") != "약초"
        or target_failure_marker.get("countBefore") != 1
        or target_failure_marker.get("countAfter") != 1
        or target_failure_marker.get("hpBefore") != target_failure_marker.get("hpAfter")
        or not item_text_provenance_matches(target_failure_marker, "herb")
        or (target_failure_marker.get("itemFeedback") or {}).get("text") != "약초 대상 없음"
        or target_failure_feedback.get("browserBattleItemFeedbackImplemented") is not True
        or target_failure_feedback.get("failed") is not True
        or target_failure_feedback.get("failureReason") != "no-target"
        or target_failure_feedback.get("text") != "약초 대상 없음"
        or target_failure_feedback.get("durationMs") != 900
        or not item_text_provenance_matches(target_failure_feedback, "herb")
        or target_failure_feedback.get("originalItemEffectFormulaImplemented") is not False
        or target_failure_feedback.get("originalStatusFormulaImplemented") is not False
        or target_failure_feedback_render.get("browserBattleItemFeedbackImplemented") is not True
        or target_failure_feedback_render.get("failed") is not True
        or target_failure_feedback_render.get("failureReason") != "no-target"
        or target_failure_feedback_render.get("text") != "약초 대상 없음"
        or not item_text_provenance_matches(target_failure_feedback_render, "herb")
        or "약초 효과 대상이 없다." not in " ".join(str(line) for line in no_target_failure.get("log") or [])
        or no_status_failure.get("scene") != "battle"
        or no_status_failure.get("map") != "map2_07e"
        or failure_before.get("name") != "Ataho"
        or failure_before.get("statuses") != []
        or failure_before.get("sourceStatuses") != []
        or "poison" not in (failure_after.get("statuses") or [])
        or "poison" not in (failure_after.get("sourceStatuses") or [])
        or no_status_failure.get("itemResult") is not True
        or (no_status_failure.get("itemCommand") or {}).get("key") != "item_2"
        or (no_status_failure.get("itemCommand") or {}).get("name") != "해독초"
        or no_status_failure.get("itemCountBefore") != 1
        or no_status_failure.get("itemCountAfter") != 1
        or no_status_failure.get("itemUse") is not None
        or no_status_failure.get("itemUseAutoSave") is not None
        or failure_counts.get("item-use-prototype")
        or int(failure_sound_counts.get("item") or 0) != 0
        or failure_marker.get("source") != "prototype-battle-item-effect"
        or failure_marker.get("failed") is not True
        or failure_marker.get("failureReason") != "no-status"
        or failure_marker.get("itemKey") != "item_2"
        or failure_marker.get("itemName") != "해독초"
        or failure_marker.get("countBefore") != 1
        or failure_marker.get("countAfter") != 1
        or failure_marker.get("statusesBefore") != []
        or failure_marker.get("statusesAfter") != []
        or not item_text_provenance_matches(failure_marker, "item_2")
        or (failure_marker.get("itemFeedback") or {}).get("text") != "해독초 회복 대상 없음"
        or failure_feedback.get("browserBattleItemFeedbackImplemented") is not True
        or failure_feedback.get("failed") is not True
        or failure_feedback.get("failureReason") != "no-status"
        or failure_feedback.get("text") != "해독초 회복 대상 없음"
        or failure_feedback.get("durationMs") != 900
        or not item_text_provenance_matches(failure_feedback, "item_2")
        or failure_feedback.get("originalItemEffectFormulaImplemented") is not False
        or failure_feedback.get("originalStatusFormulaImplemented") is not False
        or failure_feedback_render.get("browserBattleItemFeedbackImplemented") is not True
        or failure_feedback_render.get("failed") is not True
        or failure_feedback_render.get("failureReason") != "no-status"
        or failure_feedback_render.get("text") != "해독초 회복 대상 없음"
        or not item_text_provenance_matches(failure_feedback_render, "item_2")
        or "해독초 해독할 상태가 없다." not in " ".join(str(line) for line in no_status_failure.get("log") or [])
        or state.get("attackResult") is not True
        or after_poison.get("name") != state.get("actorBefore")
        or "poison" not in (after_poison.get("statuses") or [])
        or "poison" not in (after_poison.get("sourceStatuses") or [])
        or "독액" not in log_text
        or "독" not in log_text
        or state.get("itemResult") is not True
        or (state.get("itemCommand") or {}).get("key") != "item_2"
        or (state.get("itemCommand") or {}).get("name") != "해독초"
        or after_cure.get("name") != after_poison.get("name")
        or "poison" in (after_cure.get("statuses") or [])
        or "poison" in (after_cure.get("sourceStatuses") or [])
        or item_use.get("source") != "prototype-battle-item-effect"
        or item_use.get("itemKey") != "item_2"
        or item_use.get("itemName") != "해독초"
        or item_use.get("targetName") != after_poison.get("name")
        or "poison" not in (item_use.get("statusesBefore") or [])
        or "poison" in (item_use.get("statusesAfter") or [])
        or not item_text_provenance_matches(item_use, "item_2")
        or not item_text_provenance_matches(item_progress_detail, "item_2")
        or (item_use.get("itemFeedback") or {}).get("text") != "독 회복"
        or item_feedback.get("targetType") != "party"
        or item_feedback.get("targetName") != after_poison.get("name")
        or item_feedback.get("itemName") != "해독초"
        or not item_text_provenance_matches(item_feedback, "item_2")
        or item_feedback.get("curedStatuses") != ["poison"]
        or item_feedback.get("durationMs") != 900
        or item_feedback.get("browserBattleItemFeedbackImplemented") is not True
        or item_feedback.get("originalItemEffectFormulaImplemented") is not False
        or item_feedback.get("originalStatusFormulaImplemented") is not False
        or item_feedback_render.get("browserBattleItemFeedbackImplemented") is not True
        or item_feedback_render.get("text") != "독 회복"
        or not item_text_provenance_matches(item_feedback_render, "item_2")
        or state.get("itemCountAfter") != 0
        or (state.get("enemyStatusInflicted") or {}).get("poison") is not True
        or counts.get("battle-start") != 1
        or counts.get("battle-victory", 0) != 0
        or battle.get("completed") is not False
        or state.get("originalStatusFormulaImplemented") is not False
        or state.get("originalEnemyAiImplemented") is not False
    ):
        raise WebDriverError(f"candidate battle status cure state is incomplete: {state!r}")


def battle_status_feedback_entries(state: dict, *, rendered: bool = False) -> list[dict]:
    key = "statusFeedbackRender" if rendered else "statusFeedbackLog"
    return [entry for entry in (state.get(key) or []) if isinstance(entry, dict)]


def battle_status_feedback_match(
    state: dict,
    *,
    source: str,
    status_key: str,
    text: str = "",
    damage: int | None = None,
    skipped: bool | None = None,
    rendered: bool = False,
) -> dict:
    for entry in battle_status_feedback_entries(state, rendered=rendered):
        if entry.get("source") != source or entry.get("statusKey") != status_key:
            continue
        if text and entry.get("text") != text:
            continue
        if damage is not None and entry.get("damage") != damage:
            continue
        if skipped is not None and entry.get("skipped") is not skipped:
            continue
        return entry
    return {}


def battle_status_feedback_sources(state: dict) -> str:
    return ",".join(
        f"{entry.get('source')}:{entry.get('statusKey')}"
        for entry in battle_status_feedback_entries(state)
    )


def battle_status_feedback_texts(state: dict) -> str:
    return ",".join(str(entry.get("text") or "") for entry in battle_status_feedback_entries(state))


def battle_status_feedback_sound_summary(state: dict) -> str:
    return ",".join(
        f"{entry.get('source')}:{entry.get('battleStatusSound')}:{entry.get('battleStatusSoundSrc')}:{entry.get('battleStatusSoundPlayed')}"
        for entry in battle_status_feedback_entries(state)
        if entry.get("battleStatusSound")
    )


def verify_battle_status_feedback_sound(feedback: dict, expected_sound: str, expected_src: str) -> None:
    if (
        feedback.get("battleStatusSound") != expected_sound
        or feedback.get("battleStatusSoundSrc") != expected_src
        or feedback.get("battleStatusSoundPlayed") is not True
    ):
        raise WebDriverError(f"missing battle status feedback sound marker: {feedback!r}")


def battle_status_feedback_rendered(state: dict) -> bool:
    return any(
        entry.get("browserBattleStatusFeedbackImplemented") is True
        for entry in battle_status_feedback_entries(state, rendered=True)
    )


def battle_status_feedback_duration(state: dict) -> int | None:
    entries = battle_status_feedback_entries(state)
    return entries[0].get("durationMs") if entries else None


def battle_item_feedback_entries(state: dict, *, rendered: bool = False) -> list[dict]:
    key = "itemFeedbackRender" if rendered else "itemFeedbackLog"
    return [entry for entry in (state.get(key) or []) if isinstance(entry, dict)]


def battle_item_feedback_match(
    state: dict,
    *,
    source: str,
    item_key: str,
    text: str = "",
    rendered: bool = False,
) -> dict:
    for entry in battle_item_feedback_entries(state, rendered=rendered):
        if entry.get("source") != source or entry.get("itemKey") != item_key:
            continue
        if text and entry.get("text") != text:
            continue
        return entry
    return {}


def battle_item_feedback_summary(state: dict) -> str:
    return ",".join(
        f"{entry.get('source')}:{entry.get('itemKey')}:{entry.get('text')}"
        for entry in battle_item_feedback_entries(state)
    )


def battle_item_feedback_rendered(state: dict) -> bool:
    return any(
        entry.get("browserBattleItemFeedbackImplemented") is True
        for entry in battle_item_feedback_entries(state, rendered=True)
    )


def battle_item_feedback_duration(state: dict) -> int | None:
    entries = battle_item_feedback_entries(state)
    return entries[0].get("durationMs") if entries else None


def verify_battle_status_persist_state(state: dict) -> None:
    summary = state.get("summary") or {}
    action = state.get("enemyAction") or {}
    after_poison = state.get("afterPoison") or {}
    turn_result = state.get("turnResult") or {}
    auto_save = state.get("autoSave") or {}
    auto_save_progress = auto_save.get("progress") or {}
    auto_save_counts = auto_save_progress.get("counts") or {}
    saved_payload = state.get("savedPayload") or {}
    saved_progress = saved_payload.get("prototypeProgress") or {}
    saved_counts = saved_progress.get("counts") or {}
    saved_character = state.get("savedCharacter") or {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    completion = state.get("completion") or {}
    battle = completion.get("battle") or {}
    log_text = " ".join(str(line) for line in state.get("log") or [])
    status_apply_feedback = battle_status_feedback_match(
        state,
        source="enemy-status-apply",
        status_key="poison",
        text="독",
    )
    status_apply_render = battle_status_feedback_match(
        state,
        source="enemy-status-apply",
        status_key="poison",
        text="독",
        rendered=True,
    )
    action_text = expected_battle_action_text_table(12)
    if (
        state.get("scene") != "battle"
        or state.get("map") != "map2_07e"
        or summary.get("candidateId") != "event-dialogue-block-038:btl_n2"
        or summary.get("battleBackground") != "btl_n2"
        or summary.get("enemyName") != "철갑 코뿔"
        or summary.get("enemyActionName") != "독액"
        or summary.get("enemyActionNameSource") != "exe-text-table-battleCommands"
        or summary.get("enemyActionTextTableKey") != action_text["key"]
        or summary.get("enemyActionTextTableIndex") != action_text["index"]
        or summary.get("enemyActionTextTableRefVaHex") != action_text["refVaHex"]
        or summary.get("enemyActionTextTableTextVaHex") != action_text["textVaHex"]
        or summary.get("enemyStatusKey") != "poison"
        or summary.get("enemyStatusName") != "독"
        or summary.get("originalStatusFormulaImplemented") is not False
        or action.get("name") != "독액"
        or action.get("statusKey") != "poison"
        or action.get("nameSource") != "exe-text-table-battleCommands"
        or action.get("textTableKey") != action_text["key"]
        or action.get("textTableIndex") != action_text["index"]
        or action.get("textTableRefVaHex") != action_text["refVaHex"]
        or action.get("textTableTextVaHex") != action_text["textVaHex"]
        or state.get("attackResult") is not True
        or after_poison.get("name") != state.get("actorBefore")
        or "poison" not in (after_poison.get("statuses") or [])
        or "poison" not in (after_poison.get("sourceStatuses") or [])
        or "독액" not in log_text
        or "독" not in log_text
        or turn_result.get("source") != "prototype-battle-turn-result"
        or turn_result.get("statusApplied") != "독"
        or "poison" in (turn_result.get("targetStatusesBefore") or [])
        or "poison" not in (turn_result.get("targetStatusesAfter") or [])
        or status_apply_feedback.get("targetType") != "party"
        or status_apply_feedback.get("targetName") != state.get("actorBefore")
        or status_apply_feedback.get("statusName") != "독"
        or status_apply_feedback.get("durationMs") != 760
        or status_apply_feedback.get("browserBattleStatusFeedbackImplemented") is not True
        or status_apply_feedback.get("originalStatusFormulaImplemented") is not False
        or status_apply_render.get("browserBattleStatusFeedbackImplemented") is not True
        or status_apply_render.get("statusName") != "독"
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "battle-turn-prototype"
        or auto_save.get("scope") != "battle-turn"
        or auto_save.get("statusApplied") != "독"
        or auto_save.get("payloadMap") != "map2_07e"
        or "poison" in (auto_save.get("targetStatusesBefore") or [])
        or "poison" not in (auto_save.get("targetStatusesAfter") or [])
        or auto_save_counts.get("battle-start") != 1
        or auto_save_counts.get("battle-turn-prototype") != 1
        or auto_save_counts.get("battle-victory", 0) != 0
        or saved_payload.get("map") != "map2_07e"
        or saved_character.get("name") != after_poison.get("name")
        or "poison" not in (saved_character.get("statuses") or [])
        or saved_counts.get("battle-start") != 1
        or saved_counts.get("battle-turn-prototype") != 1
        or saved_counts.get("battle-victory", 0) != 0
        or (state.get("enemyStatusInflicted") or {}).get("poison") is not True
        or counts.get("battle-start") != 1
        or counts.get("battle-turn-prototype") != 1
        or counts.get("battle-victory", 0) != 0
        or battle.get("completed") is not False
        or state.get("originalStatusFormulaImplemented") is not False
        or state.get("originalEnemyAiImplemented") is not False
    ):
        raise WebDriverError(f"candidate battle status persist state is incomplete: {state!r}")


def verify_battle_status_effect_turn_state(state: dict) -> None:
    summary = state.get("summary") or {}
    after_first = state.get("afterFirst") or {}
    first_turn = state.get("firstTurnResult") or {}
    first_attack_animation = state.get("firstEnemyAttackAnimation") or {}
    first_attack_render = state.get("firstEnemyAttackAnimationRender") or {}
    status_effect = state.get("statusEffect") or {}
    effect_rows = status_effect.get("effects") or []
    poison_effect = effect_rows[0] if effect_rows else {}
    second_turn = state.get("secondTurnResult") or {}
    second_attack_animation = state.get("secondEnemyAttackAnimation") or {}
    second_attack_render = state.get("secondEnemyAttackAnimationRender") or {}
    second_effects = second_turn.get("statusEffects") or []
    second_poison = second_effects[0] if second_effects else {}
    auto_save = state.get("autoSave") or {}
    auto_effects = auto_save.get("statusEffects") or []
    auto_poison = auto_effects[0] if auto_effects else {}
    saved_payload = state.get("savedPayload") or {}
    saved_character = state.get("savedCharacter") or {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    log_text = " ".join(str(line) for line in state.get("log") or [])
    status_apply_feedback = battle_status_feedback_match(
        state,
        source="enemy-status-apply",
        status_key="poison",
        text="독",
    )
    poison_damage_feedback = battle_status_feedback_match(
        state,
        source="battle-status-poison-damage",
        status_key="poison",
        text="독 -3",
        damage=3,
    )
    poison_damage_render = battle_status_feedback_match(
        state,
        source="battle-status-poison-damage",
        status_key="poison",
        text="독 -3",
        damage=3,
        rendered=True,
    )
    verify_battle_status_feedback_sound(poison_damage_feedback, "battleHit", "../extract_wlk/09.wav")
    verify_battle_status_feedback_sound(poison_damage_render, "battleHit", "../extract_wlk/09.wav")
    action_text = expected_battle_action_text_table(12)
    first_attack_frames = first_attack_animation.get("frames") or []
    second_attack_frames = second_attack_animation.get("frames") or []
    first_special_start = max(0, int((first_attack_animation.get("frameCount") or 0) // 2))
    second_special_start = max(0, int((second_attack_animation.get("frameCount") or 0) // 2))
    if (
        state.get("scene") != "battle"
        or state.get("map") != "map2_07e"
        or summary.get("candidateId") != "event-dialogue-block-038:btl_n2"
        or summary.get("battleBackground") != "btl_n2"
        or summary.get("enemyName") != "철갑 코뿔"
        or summary.get("enemyActionName") != "독액"
        or summary.get("enemyActionNameSource") != "exe-text-table-battleCommands"
        or summary.get("enemyActionTextTableKey") != action_text["key"]
        or summary.get("enemyActionTextTableIndex") != action_text["index"]
        or summary.get("enemyActionTextTableRefVaHex") != action_text["refVaHex"]
        or summary.get("enemyActionTextTableTextVaHex") != action_text["textVaHex"]
        or summary.get("enemyStatusKey") != "poison"
        or state.get("firstActor") != "Ataho"
        or state.get("secondActor") != "Ataho"
        or state.get("firstAttackResult") is not True
        or state.get("secondAttackResult") is not True
        or after_first.get("name") != "Ataho"
        or after_first.get("hp") != 28
        or "poison" not in (after_first.get("statuses") or [])
        or first_turn.get("statusApplied") != "독"
        or first_turn.get("targetHpBefore") != 36
        or first_turn.get("targetHpAfter") != 28
        or (first_turn.get("enemyAttackAnimation") or {}).get("actionId") != "special-or-reaction"
        or first_attack_animation.get("source") != "prototype-enemy-attack-animation"
        or first_attack_animation.get("actionId") != "special-or-reaction"
        or first_attack_animation.get("actionName") != "독액"
        or first_attack_animation.get("enemyName") != "철갑 코뿔"
        or first_attack_animation.get("enemyAssetKey") != "zkb_sai"
        or first_attack_animation.get("enemyCns") != "zkb_sai.cns"
        or first_attack_animation.get("statusKey") != "poison"
        or first_attack_animation.get("actionSubtitle", "").startswith("상태 부여 후보") is not True
        or first_attack_frames[:1] != [first_special_start]
        or len(first_attack_frames) != min(4, first_attack_animation.get("frameCount") or 0)
        or first_attack_animation.get("browserBattleEnemyAttackAnimationImplemented") is not True
        or first_attack_animation.get("prototypeMonsterAttackFrameRuntimeImplemented") is not True
        or first_attack_animation.get("originalAttackSequenceBound") is not False
        or first_attack_animation.get("originalEnemyAiImplemented") is not False
        or first_attack_render.get("source") != "prototype-enemy-attack-animation-render"
        or first_attack_render.get("actionId") != "special-or-reaction"
        or first_attack_render.get("frameIndex") not in first_attack_frames
        or first_attack_render.get("enemyAttackSpriteDrawn") is not True
        or first_attack_render.get("browserBattleEnemyAttackAnimationImplemented") is not True
        or state.get("hpBeforeSecondCommand") != 28
        or status_effect.get("source") != "prototype-battle-status-effect"
        or status_effect.get("actorName") != "Ataho"
        or status_effect.get("damageTotal") != 3
        or status_effect.get("skipped") is not False
        or status_effect.get("prototypeStatusEffectImplemented") is not True
        or status_effect.get("originalStatusFormulaImplemented") is not False
        or poison_effect.get("key") != "poison"
        or poison_effect.get("name") != "독"
        or poison_effect.get("damage") != 3
        or poison_effect.get("hpBefore") != 28
        or poison_effect.get("hpAfter") != 25
        or poison_effect.get("source") != "prototype-battle-status-effect"
        or ((status_effect.get("progressEvent") or {}).get("kind")) != "battle-status-effect-prototype"
        or second_turn.get("source") != "prototype-battle-turn-result"
        or second_turn.get("commandName") != "정권"
        or second_turn.get("targetHpBefore") != 25
        or second_turn.get("targetHpAfter") != 17
        or second_turn.get("damage") != 8
        or second_turn.get("statusEffectDamage") != 3
        or second_turn.get("statusEffectSkipped") is not False
        or second_turn.get("prototypeStatusEffectImplemented") is not True
        or (second_turn.get("enemyAttackAnimation") or {}).get("actionId") != "special-or-reaction"
        or second_attack_animation.get("source") != "prototype-enemy-attack-animation"
        or second_attack_animation.get("actionId") != "special-or-reaction"
        or second_attack_animation.get("actionName") != "독액"
        or second_attack_animation.get("enemyAssetKey") != "zkb_sai"
        or second_attack_animation.get("enemyCns") != "zkb_sai.cns"
        or second_attack_animation.get("statusKey") != "poison"
        or second_attack_frames[:1] != [second_special_start]
        or len(second_attack_frames) != min(4, second_attack_animation.get("frameCount") or 0)
        or second_attack_render.get("source") != "prototype-enemy-attack-animation-render"
        or second_attack_render.get("actionId") != "special-or-reaction"
        or second_attack_render.get("frameIndex") not in second_attack_frames
        or second_attack_render.get("enemyAttackSpriteDrawn") is not True
        or second_poison.get("key") != "poison"
        or second_poison.get("damage") != 3
        or second_poison.get("hpBefore") != 28
        or second_poison.get("hpAfter") != 25
        or "poison" not in (second_turn.get("targetStatusesBefore") or [])
        or "poison" not in (second_turn.get("targetStatusesAfter") or [])
        or status_apply_feedback.get("browserBattleStatusFeedbackImplemented") is not True
        or status_apply_feedback.get("durationMs") != 760
        or poison_damage_feedback.get("targetType") != "party"
        or poison_damage_feedback.get("targetName") != "Ataho"
        or poison_damage_feedback.get("statusName") != "독"
        or poison_damage_feedback.get("durationMs") != 760
        or poison_damage_feedback.get("browserBattleStatusFeedbackImplemented") is not True
        or poison_damage_feedback.get("originalStatusFormulaImplemented") is not False
        or poison_damage_feedback.get("originalCombatFormulaImplemented") is not False
        or poison_damage_render.get("browserBattleStatusFeedbackImplemented") is not True
        or poison_damage_render.get("text") != "독 -3"
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "battle-turn-prototype"
        or auto_save.get("scope") != "battle-turn"
        or auto_save.get("statusEffectDamage") != 3
        or auto_poison.get("key") != "poison"
        or auto_poison.get("damage") != 3
        or auto_poison.get("hpBefore") != 28
        or auto_poison.get("hpAfter") != 25
        or auto_save.get("targetHpBefore") != 25
        or auto_save.get("targetHpAfter") != 17
        or saved_payload.get("map") != "map2_07e"
        or saved_character.get("name") != "Ataho"
        or saved_character.get("hp") != 17
        or "poison" not in (saved_character.get("statuses") or [])
        or counts.get("battle-start") != 1
        or counts.get("battle-turn-prototype") != 2
        or counts.get("battle-status-effect-prototype") != 1
        or counts.get("battle-victory", 0) != 0
        or "Ataho 독 3" not in log_text
        or state.get("originalStatusFormulaImplemented") is not False
        or state.get("originalEnemyAiImplemented") is not False
        or state.get("originalCombatFormulaImplemented") is not False
    ):
        raise WebDriverError(f"candidate battle status effect turn state is incomplete: {state!r}")


def verify_battle_paralysis_effect_turn_state(state: dict) -> None:
    summary = state.get("summary") or {}
    after_first = state.get("afterFirst") or {}
    first_turn = state.get("firstTurnResult") or {}
    status_effect = state.get("statusEffect") or {}
    effect_rows = status_effect.get("effects") or []
    paralysis_effect = effect_rows[0] if effect_rows else {}
    second_turn = state.get("secondTurnResult") or {}
    second_effects = second_turn.get("statusEffects") or []
    second_paralysis = second_effects[0] if second_effects else {}
    auto_save = state.get("autoSave") or {}
    auto_effects = auto_save.get("statusEffects") or []
    auto_paralysis = auto_effects[0] if auto_effects else {}
    saved_payload = state.get("savedPayload") or {}
    saved_character = state.get("savedCharacter") or {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    log_text = " ".join(str(line) for line in state.get("log") or [])
    status_apply_feedback = battle_status_feedback_match(
        state,
        source="enemy-status-apply",
        status_key="paralysis",
        text="마비",
    )
    paralysis_skip_feedback = battle_status_feedback_match(
        state,
        source="battle-status-skip-turn",
        status_key="paralysis",
        text="마비 행동불가",
        skipped=True,
    )
    paralysis_skip_render = battle_status_feedback_match(
        state,
        source="battle-status-skip-turn",
        status_key="paralysis",
        text="마비 행동불가",
        skipped=True,
        rendered=True,
    )
    verify_battle_status_feedback_sound(paralysis_skip_feedback, "menuMove", "../extract_wlk/03.wav")
    verify_battle_status_feedback_sound(paralysis_skip_render, "menuMove", "../extract_wlk/03.wav")
    if (
        state.get("scene") != "battle"
        or state.get("map") != "map1_01a"
        or summary.get("candidateId") != "event-dialogue-block-041:btl_a1"
        or summary.get("battleBackground") != "btl_a1"
        or summary.get("enemyName") != "보스 후보"
        or summary.get("enemyHp") != 96
        or summary.get("enemyAtk") != 9
        or summary.get("enemyDef") != 3
        or summary.get("enemyStatusKey") != "paralysis"
        or summary.get("enemyStatusName") != "마비"
        or state.get("firstActor") != "Ataho"
        or state.get("secondActor") != "Ataho"
        or state.get("firstAttackResult") is not True
        or state.get("secondCommandResult") is not True
        or after_first.get("name") != "Ataho"
        or after_first.get("hp") != 27
        or "paralysis" not in (after_first.get("statuses") or [])
        or first_turn.get("statusApplied") != "마비"
        or first_turn.get("targetHpBefore") != 36
        or first_turn.get("targetHpAfter") != 27
        or first_turn.get("damage") != 9
        or state.get("enemyHpAfterFirst") != 90
        or state.get("hpBeforeSecondCommand") != 27
        or state.get("enemyHpBeforeSecondCommand") != 90
        or state.get("enemyHpAfterSecondCommand") != 90
        or status_effect.get("source") != "prototype-battle-status-effect"
        or status_effect.get("actorName") != "Ataho"
        or status_effect.get("damageTotal") != 0
        or status_effect.get("skipped") is not True
        or status_effect.get("prototypeStatusEffectImplemented") is not True
        or status_effect.get("originalStatusFormulaImplemented") is not False
        or paralysis_effect.get("key") != "paralysis"
        or paralysis_effect.get("name") != "마비"
        or paralysis_effect.get("skipped") is not True
        or paralysis_effect.get("source") != "prototype-battle-status-effect"
        or ((status_effect.get("progressEvent") or {}).get("kind")) != "battle-status-effect-prototype"
        or second_turn.get("source") != "prototype-battle-turn-result"
        or second_turn.get("commandName") != "마비"
        or second_turn.get("commandKey") != "status:skip-turn"
        or second_turn.get("targetHpBefore") != 27
        or second_turn.get("targetHpAfter") != 18
        or second_turn.get("damage") != 9
        or second_turn.get("statusEffectDamage") != 0
        or second_turn.get("statusEffectSkipped") is not True
        or second_turn.get("prototypeStatusEffectImplemented") is not True
        or second_paralysis.get("key") != "paralysis"
        or second_paralysis.get("skipped") is not True
        or "paralysis" not in (second_turn.get("targetStatusesBefore") or [])
        or "paralysis" not in (second_turn.get("targetStatusesAfter") or [])
        or status_apply_feedback.get("browserBattleStatusFeedbackImplemented") is not True
        or status_apply_feedback.get("durationMs") != 760
        or paralysis_skip_feedback.get("targetType") != "party"
        or paralysis_skip_feedback.get("targetName") != "Ataho"
        or paralysis_skip_feedback.get("statusName") != "마비"
        or paralysis_skip_feedback.get("durationMs") != 760
        or paralysis_skip_feedback.get("browserBattleStatusFeedbackImplemented") is not True
        or paralysis_skip_feedback.get("originalStatusFormulaImplemented") is not False
        or paralysis_skip_feedback.get("originalCombatFormulaImplemented") is not False
        or paralysis_skip_render.get("browserBattleStatusFeedbackImplemented") is not True
        or paralysis_skip_render.get("text") != "마비 행동불가"
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "battle-turn-prototype"
        or auto_save.get("scope") != "battle-turn"
        or auto_save.get("statusEffectDamage") != 0
        or auto_save.get("statusEffectSkipped") is not True
        or auto_paralysis.get("key") != "paralysis"
        or auto_paralysis.get("skipped") is not True
        or auto_save.get("targetHpBefore") != 27
        or auto_save.get("targetHpAfter") != 18
        or saved_payload.get("map") != "map1_01a"
        or saved_character.get("name") != "Ataho"
        or saved_character.get("hp") != 18
        or "paralysis" not in (saved_character.get("statuses") or [])
        or counts.get("battle-start") != 1
        or counts.get("battle-turn-prototype") != 2
        or counts.get("battle-status-effect-prototype") != 1
        or counts.get("battle-victory", 0) != 0
        or "Ataho 마비 행동 불가" not in log_text
        or state.get("originalStatusFormulaImplemented") is not False
        or state.get("originalEnemyAiImplemented") is not False
        or state.get("originalCombatFormulaImplemented") is not False
    ):
        raise WebDriverError(f"candidate battle paralysis effect turn state is incomplete: {state!r}")


def verify_battle_status_persist_title_continue_state(
    title_state: dict,
    click_state: dict,
    restored_state: dict,
) -> None:
    title_labels = [str(label) for label in (title_state.get("titleMenuLabels") or [])]
    click_labels = [str(label) for label in (click_state.get("titleMenuLabels") or [])]
    progress = restored_state.get("progress") or {}
    counts = progress.get("counts") or {}
    completion = restored_state.get("completion") or {}
    battle = completion.get("battle") or {}
    poisoned = restored_state.get("poisoned") or {}
    status_review = restored_state.get("statusReview") or {}
    status_lines = " ".join(str(line) for line in status_review.get("lines") or [])
    objective_before = restored_state.get("objectiveBefore") or {}
    objective_action = restored_state.get("objectiveAction") or {}
    objective_notice = restored_state.get("objectiveCompletionNotice") or {}
    objective_dialogue = restored_state.get("objectiveActiveDialogueBlock") or {}
    turn_feedback_log = restored_state.get("battleTurnCompletionFeedbackLog") or []
    turn_feedback_render_log = restored_state.get("battleTurnCompletionFeedbackRender") or []
    turn_feedback = restored_state.get("battleTurnCompletionFeedbackLast") or {}
    turn_feedback_render = restored_state.get("battleTurnCompletionFeedbackLastRender") or {}
    notice_feedback = objective_notice.get("battleTurnCompletionFeedback") or {}
    notice_lines = " ".join(str(line) for line in objective_notice.get("lines") or [])
    runtime_state = restored_state.get("runtimeState") or {}
    runtime_poisoned = next(
        (member for member in (runtime_state.get("characters") or []) if "poison" in (member.get("statuses") or [])),
        {},
    )
    saved_payload = restored_state.get("savedPayload") or {}
    saved_character = next(
        (
            member
            for member in ((saved_payload.get("runtimeState") or {}).get("characters") or [])
            if "poison" in (member.get("statuses") or [])
        ),
        {},
    )
    if (
        title_state.get("scene") != "title"
        or title_state.get("quickLoadHidden") is not False
        or title_state.get("quickLoadText") != "이어하기"
        or "continue" not in (title_state.get("titleMenuKeys") or [])
        or not any("이어하기 map2_07e 11,11" in label for label in title_labels)
        or not isinstance(click_state, dict)
        or click_state.get("ok") is not True
        or click_state.get("text") != "이어하기"
        or not any("이어하기 map2_07e 11,11" in label for label in click_labels)
        or restored_state.get("titleContinue") is not True
        or restored_state.get("scene") != "map"
        or restored_state.get("map") != "map2_07e"
        or "map=map2_07e" not in str(restored_state.get("search") or "")
        or "startTile=11%2C11" not in str(restored_state.get("search") or "")
        or counts.get("battle-start") != 1
        or counts.get("battle-turn-prototype") != 1
        or counts.get("battle-victory", 0) != 0
        or battle.get("completed") is not False
        or poisoned.get("name") != "Ataho"
        or "poison" not in (poisoned.get("statuses") or [])
        or runtime_poisoned.get("name") != "Ataho"
        or saved_character.get("name") != "Ataho"
        or status_review.get("blockId") != "status-menu:map2_07e"
        or "상태 독" not in status_lines
        or objective_before.get("title") != "후보 전투 턴 완료 btl_n2"
        or objective_before.get("source") != "prototype-battle-turn-completion"
        or objective_before.get("nextAction") != "완료 알림 확인"
        or restored_state.get("objectiveResult") is not True
        or objective_action.get("action") != "battle-turn-completion-notice"
        or objective_action.get("activeId") != "battle-turn-complete:map2_07e"
        or (objective_action.get("objective") or {}).get("title") != "후보 전투 턴 완료 btl_n2"
        or objective_notice.get("blockId") != "battle-turn-complete:map2_07e"
        or objective_dialogue.get("blockId") != "battle-turn-complete:map2_07e"
        or "전투 턴 완료 1/1" not in notice_lines
        or "btl_n2" not in notice_lines
        or "독액" not in notice_lines
        or "상태 독" not in notice_lines
        or "Ataho HP" not in notice_lines
        or notice_feedback.get("source") != "battle-turn-completion-notice-feedback"
        or notice_feedback.get("text") != "전투 턴 완료 1/1"
        or notice_feedback.get("battleCompletionSound") != "menuConfirm"
        or notice_feedback.get("battleCompletionSoundSrc") != "../extract_wlk/04.wav"
        or notice_feedback.get("battleCompletionSoundPlayed") is not True
        or notice_feedback.get("browserBattleCompletionFeedbackImplemented") is not True
        or notice_feedback.get("prototypeBattleTurnImplemented") is not True
        or not turn_feedback_log
        or not turn_feedback_render_log
        or turn_feedback.get("source") != "battle-turn-completion-notice-feedback"
        or turn_feedback.get("text") != "전투 턴 완료 1/1"
        or turn_feedback.get("battleCompletionSound") != "menuConfirm"
        or turn_feedback.get("battleCompletionSoundSrc") != "../extract_wlk/04.wav"
        or turn_feedback.get("battleCompletionSoundPlayed") is not True
        or turn_feedback.get("browserBattleCompletionFeedbackImplemented") is not True
        or turn_feedback.get("prototypeBattleTurnImplemented") is not True
        or turn_feedback_render.get("source") != "battle-turn-completion-notice-feedback"
        or turn_feedback_render.get("text") != "전투 턴 완료 1/1"
        or turn_feedback_render.get("battleCompletionSound") != "menuConfirm"
        or turn_feedback_render.get("battleCompletionSoundSrc") != "../extract_wlk/04.wav"
        or turn_feedback_render.get("battleCompletionSoundPlayed") is not True
        or turn_feedback_render.get("browserBattleCompletionFeedbackImplemented") is not True
        or turn_feedback_render.get("prototypeBattleTurnImplemented") is not True
        or restored_state.get("quickLoadHidden") is not False
        or restored_state.get("quickLoadText") != "임시 불러오기"
        or restored_state.get("loadedSaveSummary") is not None
        or restored_state.get("originalStatusFormulaImplemented") is not False
        or restored_state.get("originalEnemyAiImplemented") is not False
    ):
        raise WebDriverError(
            "candidate battle status title continue did not restore poison status: "
            f"title={title_state!r} click={click_state!r} restored={restored_state!r}"
        )


def verify_field_encounter_state(state: dict) -> None:
    marker = state.get("marker") or {}
    step_marker = state.get("stepMarker") or {}
    start_snapshot = state.get("startSnapshot") or {}
    summary = start_snapshot.get("summary") or {}
    victory_summary = state.get("victorySummary") or {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    record = state.get("progressRecord") or {}
    detail = record.get("detail") or {}
    runtime_state = state.get("runtimeState") or {}
    herb = next((item for item in runtime_state.get("items") or [] if item.get("key") == "herb"), {})
    victory_auto_save = state.get("victoryAutoSave") or {}
    saved_payload = state.get("savedPayload") or {}
    saved_tile = saved_payload.get("tile") or {}
    saved_field = saved_payload.get("fieldEncounter") or {}
    saved_progress = saved_payload.get("prototypeProgress") or {}
    saved_counts = saved_progress.get("counts") or {}
    saved_runtime = saved_payload.get("runtimeState") or {}
    saved_herb = next((item for item in saved_runtime.get("items") or [] if item.get("key") == "herb"), {})
    completion = state.get("completion") or {}
    battle_completion = completion.get("battle") or {}
    field_completion = completion.get("fieldEncounter") or {}
    objective_before = state.get("objectiveBefore") or {}
    objective_action = state.get("objectiveAction") or {}
    objective_notice = state.get("objectiveCompletionNotice") or {}
    objective_dialogue = state.get("objectiveActiveDialogueBlock") or {}
    objective_notice_lines = "\n".join(objective_notice.get("lines") or [])
    save_state = state.get("state") or {}
    victory_sound = victory_summary.get("victorySound") or {}
    auto_save_victory_sound = victory_auto_save.get("victorySound") or {}
    start_music = start_snapshot.get("musicCue") or {}
    end_music = state.get("musicCue") or {}
    end_music_marker = state.get("battleEndMusicCue") or {}
    music_log = state.get("musicCueLog") or []
    music_log_cues = [row.get("cue") for row in music_log if isinstance(row, dict)]
    music_log_reasons = [row.get("reason") for row in music_log if isinstance(row, dict)]
    music_synth = state.get("musicSynth") or {}
    music_stop = state.get("musicSynthStop") or {}
    before = state.get("before") or {}
    before_payload = before.get("savePayload") or {}
    movement_steps = state.get("movementSteps") or []
    movement_codes = [step.get("code") for step in movement_steps]
    first_step = movement_steps[0] if movement_steps else {}
    fifth_step = movement_steps[4] if len(movement_steps) >= 5 else {}
    final_step = movement_steps[-1] if movement_steps else {}
    fifth_field = fifth_step.get("fieldEncounter") or {}
    fifth_auto_save = fifth_step.get("stepAutoSave") or {}
    final_step_marker = final_step.get("stepMarker") or {}
    trigger_feedback = next(
        (
            entry
            for entry in (state.get("encounterFeedbackLog") or [])
            if isinstance(entry, dict)
            and entry.get("source") == "field-encounter-trigger-feedback"
            and entry.get("text") == "전투 발생 btl_b1"
        ),
        {},
    )
    trigger_feedback_render = next(
        (
            entry
            for entry in (state.get("encounterFeedbackRender") or [])
            if isinstance(entry, dict)
            and entry.get("source") == "field-encounter-trigger-feedback"
            and entry.get("text") == "전투 발생 btl_b1"
        ),
        {},
    )
    verify_field_encounter_completion_notice_feedback(state)
    first_before_foot = first_step.get("beforeFoot") or {}
    first_after_foot = first_step.get("afterFoot") or {}
    final_before_foot = final_step.get("beforeFoot") or {}
    final_after_foot = final_step.get("afterFoot") or {}
    candidate = start_snapshot.get("battleCandidate") or {}
    verify_battle_start_feedback(
        start_snapshot,
        {
            "candidateId": "event-dialogue-block-015:btl_b1",
            "blockId": "event-dialogue-block-015",
            "battleBackground": "btl_b1",
            "enemyName": "푸른 마수",
        },
        field_encounter=True,
    )
    if (
        state.get("started") is not True
        or state.get("movementInput") is not True
        or len(movement_steps) != 6
        or movement_codes != ["ArrowRight", "ArrowLeft", "ArrowRight", "ArrowLeft", "ArrowRight", "ArrowLeft"]
        or not all((step.get("tileChanged") is True) for step in movement_steps)
        or first_before_foot.get("x") != 11
        or first_before_foot.get("y") != 12
        or first_after_foot.get("x") != 12
        or first_after_foot.get("y") != 12
        or fifth_field.get("stepCount") != 5
        or fifth_auto_save.get("source") != "field-encounter-step"
        or final_before_foot.get("x") != 12
        or final_before_foot.get("y") != 12
        or final_after_foot.get("x") != 11
        or final_after_foot.get("y") != 12
        or final_step_marker.get("triggered") is not True
        or final_step_marker.get("stepCount") != 6
        or (final_step_marker.get("feedback") or {}).get("source") != "field-encounter-trigger-feedback"
        or (final_step_marker.get("feedback") or {}).get("text") != "전투 발생 btl_b1"
        or (final_step_marker.get("feedback") or {}).get("fieldEncounterSound") != "step"
        or not str((final_step_marker.get("feedback") or {}).get("fieldEncounterSoundSrc") or "").endswith("/extract_wlk/00.wav")
        or (final_step_marker.get("feedback") or {}).get("fieldEncounterSoundPlayed") is not True
        or (final_step_marker.get("feedback") or {}).get("browserFieldEncounterFeedbackImplemented") is not True
        or trigger_feedback.get("stepCount") != 6
        or trigger_feedback.get("threshold") != 6
        or trigger_feedback.get("triggered") is not True
        or trigger_feedback.get("battleBackground") != "btl_b1"
        or trigger_feedback.get("durationMs") != 900
        or trigger_feedback.get("fieldEncounterSound") != "step"
        or not str(trigger_feedback.get("fieldEncounterSoundSrc") or "").endswith("/extract_wlk/00.wav")
        or trigger_feedback.get("fieldEncounterSoundPlayed") is not True
        or trigger_feedback.get("browserFieldEncounterFeedbackImplemented") is not True
        or trigger_feedback.get("originalEncounterTableMapped") is not False
        or trigger_feedback.get("originalEncounterRuntimeImplemented") is not False
        or trigger_feedback.get("originalStoryFlagRuntimeImplemented") is not False
        or trigger_feedback_render.get("active") is not True
        or trigger_feedback_render.get("stepCount") != 6
        or trigger_feedback_render.get("threshold") != 6
        or trigger_feedback_render.get("triggered") is not True
        or trigger_feedback_render.get("battleBackground") != "btl_b1"
        or trigger_feedback_render.get("durationMs") != 900
        or trigger_feedback_render.get("fieldEncounterSound") != "step"
        or not str(trigger_feedback_render.get("fieldEncounterSoundSrc") or "").endswith("/extract_wlk/00.wav")
        or trigger_feedback_render.get("fieldEncounterSoundPlayed") is not True
        or trigger_feedback_render.get("browserFieldEncounterFeedbackImplemented") is not True
        or start_snapshot.get("scene") != "battle"
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or state.get("loadedSaveSummary") is not None
        or state.get("menuLabel") != "전투 탐색 끄기 0/6"
        or state.get("buttonText") != "전투 btl_b1"
        or state.get("victoryResult") is not True
        or state.get("closeResult") is not True
        or victory_auto_save.get("saved") is not True
        or victory_auto_save.get("source") != "field-encounter-victory"
        or victory_auto_save.get("map") != "map1_02b"
        or victory_auto_save.get("payloadMap") != "map1_02b"
        or (victory_auto_save.get("tile") or {}).get("x") != 11
        or (victory_auto_save.get("tile") or {}).get("y") != 12
        or (victory_auto_save.get("payloadTile") or {}).get("x") != 11
        or (victory_auto_save.get("payloadTile") or {}).get("y") != 12
        or (victory_auto_save.get("fieldEncounter") or {}).get("enabled") is not True
        or (victory_auto_save.get("fieldEncounter") or {}).get("stepCount") != 0
        or ((victory_auto_save.get("progress") or {}).get("counts") or {}).get("field-encounter") != 1
        or ((victory_auto_save.get("progress") or {}).get("counts") or {}).get("field-encounter-victory") != 1
        or ((victory_auto_save.get("progress") or {}).get("counts") or {}).get("battle-victory", 0) != 0
        or victory_auto_save.get("runtimeMoney") != 75
        or next((item for item in victory_auto_save.get("runtimeItems") or [] if item.get("key") == "herb"), {}).get("count") != 2
        or victory_auto_save.get("originalEncounterRuntimeImplemented") is not False
        or victory_auto_save.get("originalStoryFlagRuntimeImplemented") is not False
        or not sound_event_matches(auto_save_victory_sound, "victory", "/extract_wlk/12.wav")
        or start_music.get("source") != "runtime-music-cue"
        or start_music.get("cue") != "battle"
        or start_music.get("src") != "../extract_mlk/08.mid"
        or start_music.get("browserRuntimeMusicCueImplemented") is not True
        or start_music.get("originalMidiPlaybackImplemented") is not False
        or end_music.get("source") != "runtime-music-cue"
        or end_music.get("cue") != "map"
        or end_music.get("src") != "../extract_mlk/01.mid"
        or end_music.get("reason") != "battle-end-map-return"
        or end_music.get("browserRuntimeMusicCueImplemented") is not True
        or end_music.get("originalMidiPlaybackImplemented") is not False
        or end_music_marker.get("cue") != "map"
        or end_music_marker.get("src") != "../extract_mlk/01.mid"
        or end_music_marker.get("reason") != "battle-end-map-return"
        or "battle" not in music_log_cues
        or music_log_cues[-1:] != ["map"]
        or "battle-start" not in music_log_reasons
        or "battle-end-map-return" not in music_log_reasons
        or music_stop.get("source") != "runtime-midi-synth-stop"
        or music_stop.get("reason") != "cue-change"
        or music_stop.get("previousCue") != "battle"
        or music_stop.get("previousSrc") != "../extract_mlk/08.mid"
        or music_stop.get("nextCue") != "map"
        or music_stop.get("nextSrc") != "../extract_mlk/01.mid"
        or music_stop.get("stoppedPreviousCue") is not True
        or music_stop.get("browserMidiSynthStopImplemented") is not True
        or music_stop.get("originalDirectMusicPlaybackImplemented") is not False
        or music_synth.get("cue") != "map"
        or music_synth.get("src") != "../extract_mlk/01.mid"
        or music_synth.get("status") not in {"scheduled", "pending-user-gesture"}
        or music_synth.get("originalDirectMusicPlaybackImplemented") is not False
        or saved_payload.get("map") != "map1_02b"
        or saved_tile.get("x") != 11
        or saved_tile.get("y") != 12
        or saved_runtime.get("money") != 75
        or saved_herb.get("name") != "약초"
        or saved_herb.get("count") != 2
        or saved_field.get("enabled") is not True
        or saved_field.get("stepCount") != 0
        or saved_field.get("lastMap") != "map1_02b"
        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 before.get("menuLabel") != "전투 탐색 끄기 0/6"
        or "encounter=1" not in str(before.get("urlSearch") or "")
        or before_payload.get("enabled") is not True
        or before_payload.get("stepCount") != 0
        or before_payload.get("source") != "prototype-field-encounter"
        or save_state.get("enabled") is not True
        or save_state.get("stepCount") != 0
        or save_state.get("source") != "prototype-field-encounter"
        or save_state.get("originalEncounterTableMapped") is not False
        or save_state.get("originalEncounterRuntimeImplemented") is not False
        or marker.get("battleStarted") is not True
        or marker.get("scene") != "battle"
        or marker.get("source") != "prototype-field-encounter"
        or marker.get("candidateId") != "event-dialogue-block-015:btl_b1"
        or marker.get("blockId") != "event-dialogue-block-015"
        or marker.get("battleBackground") != "btl_b1"
        or marker.get("steps") != 6
        or marker.get("threshold") != 6
        or marker.get("originalEncounterTableMapped") is not False
        or marker.get("originalEncounterRuntimeImplemented") is not False
        or marker.get("originalEventDrivenBattleEntry") is not False
        or marker.get("originalStoryFlagRuntimeImplemented") is not False
        or step_marker.get("triggered") is not True
        or step_marker.get("stepCount") != 6
        or step_marker.get("candidateId") != "event-dialogue-block-015:btl_b1"
        or summary.get("map") != "map1_02b"
        or summary.get("candidateId") != "event-dialogue-block-015:btl_b1"
        or summary.get("blockId") != "event-dialogue-block-015"
        or summary.get("battleBackground") != "btl_b1"
        or summary.get("sourceBacked") is not True
        or summary.get("originalEventDrivenBattleEntry") is not False
        or summary.get("repeatableFieldEncounter") is not True
        or summary.get("originalEncounterTableMapped") is not False
        or summary.get("originalEncounterRuntimeImplemented") is not False
        or candidate.get("id") != "event-dialogue-block-015:btl_b1"
        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 record.get("kind") != "field-encounter-victory"
        or record.get("id") != "field-encounter:event-dialogue-block-015"
        or detail.get("source") != "prototype-field-encounter"
        or detail.get("fieldEncounter") is not True
        or detail.get("repeatableFieldEncounter") is not True
        or detail.get("itemKey") != "herb"
        or detail.get("itemName") != "약초"
        or not item_text_provenance_matches(detail, "herb")
        or detail.get("dropCount") != 1
        or detail.get("dropCountBefore") != 1
        or detail.get("dropCountAfter") != 2
        or detail.get("dropCountGranted") != 1
        or detail.get("dropSource") != "prototype-enemy-drop"
        or detail.get("originalRewardTableMapped") is not False
        or detail.get("originalDropTableMapped") is not False
        or detail.get("originalEncounterTableMapped") is not False
        or detail.get("originalEncounterRuntimeImplemented") is not False
        or detail.get("originalEventDrivenBattleEntry") is not False
        or detail.get("originalStoryFlagRuntimeImplemented") is not False
        or victory_summary.get("rewardGranted") is not True
        or (victory_summary.get("itemDrop") or {}).get("itemKey") != "herb"
        or (victory_summary.get("itemDrop") or {}).get("itemName") != "약초"
        or (victory_summary.get("itemDrop") or {}).get("countAfter") != 2
        or not item_text_provenance_matches(victory_summary.get("itemDrop") or {}, "herb")
        or not sound_event_matches(victory_sound, "victory", "/extract_wlk/12.wav")
        or herb.get("name") != "약초"
        or herb.get("count") != 2
        or ((victory_summary.get("progressEvent") or {}).get("kind")) != "field-encounter-victory"
        or battle_completion.get("id") != "event-dialogue-block-015"
        or battle_completion.get("completed") is not False
        or battle_completion.get("completedCount") != 0
        or battle_completion.get("remainingCount") != 1
        or field_completion.get("id") != "field-encounter:event-dialogue-block-015"
        or field_completion.get("completed") is not True
        or field_completion.get("completedCount") != 1
        or field_completion.get("remainingCount") != 0
        or field_completion.get("battleBackground") != "btl_b1"
        or field_completion.get("itemName") != "약초"
        or field_completion.get("dropCountAfter") != 2
        or field_completion.get("source") != "prototype-field-encounter"
        or field_completion.get("repeatableFieldEncounter") is not True
        or field_completion.get("originalEncounterTableMapped") is not False
        or field_completion.get("originalEncounterRuntimeImplemented") is not False
        or field_completion.get("originalEventDrivenBattleEntry") is not False
        or field_completion.get("originalRewardTableMapped") is not False
        or field_completion.get("originalDropTableMapped") is not False
        or field_completion.get("originalStoryFlagRuntimeImplemented") is not False
        or objective_before.get("title") != "후보 필드 전투 완료 btl_b1"
        or objective_before.get("nextAction") != "완료 알림 확인"
        or objective_before.get("source") != "prototype-field-encounter-completion"
        or state.get("objectiveResult") is not True
        or objective_action.get("action") != "field-encounter-completion-notice"
        or objective_action.get("handled") is not True
        or objective_action.get("activeId") != "field-encounter-complete:map1_02b"
        or objective_action.get("repeatableFieldEncounter") is not True
        or objective_action.get("originalEncounterRuntimeImplemented") is not False
        or objective_action.get("originalRewardTableMapped") is not False
        or objective_action.get("originalDropTableMapped") is not False
        or objective_notice.get("blockId") != "field-encounter-complete:map1_02b"
        or objective_notice.get("map") != "map1_02b"
        or "필드 전투 완료 1/1" not in objective_notice_lines
        or "btl_b1" not in objective_notice_lines
        or "필드 전투 보상 소지금 75 / 경험치 +7" not in objective_notice_lines
        or "필드 전투 드롭 약초 2" not in objective_notice_lines
        or "원본 encounter table/runtime/story flag 실행 증명은 아직 아닙니다." not in objective_notice_lines
        or (objective_notice.get("completion") or {}).get("completed") is not True
        or objective_notice.get("originalEncounterRuntimeImplemented") is not False
        or objective_notice.get("originalStoryFlagRuntimeImplemented") is not False
        or objective_dialogue.get("blockId") != "field-encounter-complete:map1_02b"
        or objective_dialogue.get("line") != "필드 전투 완료 1/1"
        or progress.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"field encounter state should start a prototype battle without original encounter promotion: {state!r}")


def verify_field_encounter_completion_notice_feedback(state: dict) -> None:
    feedback = state.get("objectiveEncounterFeedbackLast") or {}
    rendered = state.get("objectiveEncounterFeedbackLastRender") or {}
    feedback_log = [
        entry
        for entry in (state.get("objectiveEncounterFeedbackLog") or [])
        if isinstance(entry, dict)
        and entry.get("source") == "field-encounter-completion-notice-feedback"
    ]
    feedback_render_log = [
        entry
        for entry in (state.get("objectiveEncounterFeedbackRender") or [])
        if isinstance(entry, dict)
        and entry.get("source") == "field-encounter-completion-notice-feedback"
    ]
    if (
        not feedback_log
        or not feedback_render_log
        or feedback.get("source") != "field-encounter-completion-notice-feedback"
        or feedback.get("text") != "필드 전투 완료 1/1"
        or feedback.get("candidateId") != "field-encounter:event-dialogue-block-015"
        or feedback.get("blockId") != "field-encounter:event-dialogue-block-015"
        or feedback.get("battleBackground") != "btl_b1"
        or feedback.get("mode") != "completed"
        or feedback.get("modeSource") != "field-encounter-completion-notice"
        or feedback.get("enabled") is not True
        or feedback.get("stepCount") != 0
        or feedback.get("threshold") != 6
        or feedback.get("triggered") is not False
        or feedback.get("durationMs") != 900
        or feedback.get("fieldEncounterSound") != "menuConfirm"
        or not str(feedback.get("fieldEncounterSoundSrc") or "").endswith("/extract_wlk/04.wav")
        or feedback.get("fieldEncounterSoundPlayed") is not True
        or feedback.get("browserFieldEncounterFeedbackImplemented") is not True
        or feedback.get("originalEncounterTableMapped") is not False
        or feedback.get("originalEncounterRuntimeImplemented") is not False
        or feedback.get("originalStoryFlagRuntimeImplemented") is not False
        or rendered.get("active") is not True
        or rendered.get("source") != "field-encounter-completion-notice-feedback"
        or rendered.get("text") != "필드 전투 완료 1/1"
        or rendered.get("candidateId") != "field-encounter:event-dialogue-block-015"
        or rendered.get("blockId") != "field-encounter:event-dialogue-block-015"
        or rendered.get("battleBackground") != "btl_b1"
        or rendered.get("mode") != "completed"
        or rendered.get("modeSource") != "field-encounter-completion-notice"
        or rendered.get("enabled") is not True
        or rendered.get("stepCount") != 0
        or rendered.get("threshold") != 6
        or rendered.get("triggered") is not False
        or rendered.get("durationMs") != 900
        or rendered.get("fieldEncounterSound") != "menuConfirm"
        or not str(rendered.get("fieldEncounterSoundSrc") or "").endswith("/extract_wlk/04.wav")
        or rendered.get("fieldEncounterSoundPlayed") is not True
        or rendered.get("browserFieldEncounterFeedbackImplemented") is not True
        or rendered.get("originalEncounterTableMapped") is not False
        or rendered.get("originalEncounterRuntimeImplemented") is not False
        or rendered.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"field encounter completion notice feedback is incomplete: {state!r}")


def field_encounter_completion_notice_feedback_summary(state: dict) -> str:
    feedback = state.get("objectiveEncounterFeedbackLast") or {}
    rendered = state.get("objectiveEncounterFeedbackLastRender") or {}
    return (
        f"fieldNoticeFeedback={feedback.get('source')}:{feedback.get('text')} "
        f"fieldNoticeFeedbackRender={rendered.get('active')} "
        f"fieldNoticeSound={feedback.get('fieldEncounterSound')}:"
        f"{feedback.get('fieldEncounterSoundSrc')}:{feedback.get('fieldEncounterSoundPlayed')}"
    )


def verify_field_encounter_title_continue_state(title_state: dict, click_state: dict, restored_state: dict) -> None:
    title_labels = [str(label) for label in (title_state.get("titleMenuLabels") or [])]
    click_labels = [str(label) for label in (click_state.get("titleMenuLabels") or [])]
    field = restored_state.get("fieldEncounter") or {}
    progress = restored_state.get("progress") or {}
    counts = progress.get("counts") or {}
    runtime = restored_state.get("runtimeState") or {}
    herb = next((item for item in runtime.get("items") or [] if item.get("key") == "herb"), {})
    battle = ((restored_state.get("completion") or {}).get("battle") or {})
    field_completion = ((restored_state.get("completion") or {}).get("fieldEncounter") or {})
    objective_before = restored_state.get("objectiveBefore") or {}
    objective_action = restored_state.get("objectiveAction") or {}
    objective_notice = restored_state.get("objectiveCompletionNotice") or {}
    objective_dialogue = restored_state.get("objectiveActiveDialogueBlock") or {}
    objective_notice_lines = "\n".join(objective_notice.get("lines") or [])
    hud_lines = [str(line) for line in restored_state.get("playHud") or []]
    verify_field_encounter_completion_notice_feedback(restored_state)
    if (
        title_state.get("scene") != "title"
        or title_state.get("quickLoadHidden") is not False
        or title_state.get("quickLoadText") != "이어하기"
        or "continue" not in (title_state.get("titleMenuKeys") or [])
        or not any("이어하기 map1_02b 11,12" in label for label in title_labels)
        or not any("전투" in label for label in title_labels)
        or not isinstance(click_state, dict)
        or click_state.get("ok") is not True
        or click_state.get("text") != "이어하기"
        or not any("이어하기 map1_02b 11,12" in label for label in click_labels)
        or restored_state.get("titleContinue") is not True
        or restored_state.get("scene") != "map"
        or restored_state.get("map") != "map1_02b"
        or "map=map1_02b" not in str(restored_state.get("search") or "")
        or "startTile=11%2C12" not in str(restored_state.get("search") or "")
        or "encounter=1" not in str(restored_state.get("search") or "")
        or field.get("enabled") is not True
        or field.get("stepCount") != 0
        or field.get("lastMap") != "map1_02b"
        or field.get("source") != "prototype-field-encounter"
        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 battle.get("completed") is not False
        or field_completion.get("id") != "field-encounter:event-dialogue-block-015"
        or field_completion.get("completed") is not True
        or field_completion.get("battleBackground") != "btl_b1"
        or field_completion.get("itemName") != "약초"
        or field_completion.get("dropCountAfter") != 2
        or field_completion.get("originalEncounterRuntimeImplemented") is not False
        or objective_before.get("title") != "후보 필드 전투 완료 btl_b1"
        or objective_before.get("nextAction") != "완료 알림 확인"
        or objective_before.get("source") != "prototype-field-encounter-completion"
        or restored_state.get("objectiveResult") is not True
        or objective_action.get("action") != "field-encounter-completion-notice"
        or objective_action.get("activeId") != "field-encounter-complete:map1_02b"
        or objective_notice.get("blockId") != "field-encounter-complete:map1_02b"
        or "필드 전투 완료 1/1" not in objective_notice_lines
        or "필드 전투 보상 소지금 75 / 경험치 +7" not in objective_notice_lines
        or "필드 전투 드롭 약초 2" not in objective_notice_lines
        or objective_dialogue.get("blockId") != "field-encounter-complete:map1_02b"
        or restored_state.get("buttonText") != "전투 btl_b1"
        or restored_state.get("quickLoadHidden") is not False
        or restored_state.get("quickLoadText") != "임시 불러오기"
        or not any("전투 0/6" in line for line in hud_lines)
        or runtime.get("money") != 75
        or herb.get("name") != "약초"
        or herb.get("count") != 2
        or restored_state.get("loadedSaveSummary") is not None
    ):
        raise WebDriverError(
            "field encounter title continue did not restore repeatable encounter/reward state: "
            f"title={title_state!r} click={click_state!r} restored={restored_state!r}"
        )


def write_report(report: dict) -> None:
    out_dir = ROOT / "out"
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "candidate_battle_browser_smoke.json").write_text(
        json.dumps(report, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    lines = [
        "# Candidate Battle Browser Smoke",
        "",
        f"- status: `{report.get('status')}`",
        f"- map-family battle: `{report.get('mapFamilyBattle')}`",
        f"- pointer battle command: `{report.get('pointerBattleCommand')}`",
        f"- battle skill action: `{report.get('battleSkillAction')}`",
        f"- action battle: `{report.get('actionBattle')}`",
        f"- battle candidate menu: `{report.get('battleCandidateMenu')}`",
        f"- battle sprite menu: `{report.get('battleSpriteMenu')}`",
        f"- battle sprite direct URL: `{report.get('battleSpriteDirectUrl')}`",
        f"- dialogue battle link: `{report.get('dialogueBattleLink')}`",
        f"- requested battle: `{report.get('requestedBattle')}`",
        f"- completed battle: `{report.get('completedBattle')}`",
        f"- completed battle title continue: `{report.get('completionTitleContinue')}`",
        f"- field encounter: `{report.get('fieldEncounter')}`",
        f"- field encounter title continue: `{report.get('fieldEncounterTitleContinue')}`",
        f"- status cure: `{report.get('statusCure')}`",
        f"- status persist: `{report.get('statusPersist')}`",
        f"- status persist title continue: `{report.get('statusPersistTitleContinue')}`",
        f"- status effect turn: `{report.get('statusEffectTurn')}`",
        f"- paralysis turn: `{report.get('paralysisTurn')}`",
        f"- run battle: `{report.get('runBattle')}`",
        f"- run battle title continue: `{report.get('runTitleContinue')}`",
        f"- defeat battle: `{report.get('defeatBattle')}`",
        f"- defeat battle title continue: `{report.get('defeatTitleContinue')}`",
        "",
    ]
    (out_dir / "candidate_battle_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_battle_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,
            )

            family_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            family_button, family_state, family_checksum = verify_battle_case(
                port,
                session_id,
                {
                    "map": "map1_02b",
                    "candidateId": "event-dialogue-block-015:btl_b1",
                    "blockId": "event-dialogue-block-015",
                    "battleBackground": "btl_b1",
                    "enemySpriteCandidate": "zky_ao.cns",
                    "enemySpriteAssetKey": "zky_ao",
                    "enemySpriteRefVaHex": "0x00490124",
                    "battleBackgroundRefVaHex": "0x0048f0bc",
                    "enemyName": "푸른 마수",
                    "enemyHp": 42,
                    "enemyAtk": 4,
                    "enemyDef": 0,
                    "enemyActionName": "할퀴기",
                    "enemyActionIndex": 3,
                    "enemyRewardExp": 7,
                    "enemyDropKey": "herb",
                    "enemyDropName": "약초",
                    "enemyDropCount": 1,
                },
            )
            pointer_attack_state = execute_js(port, session_id, battle_pointer_attack_script(), timeout=3)
            verify_battle_pointer_attack(
                pointer_attack_state,
                {
                    "candidateId": "event-dialogue-block-015:btl_b1",
                    "battleBackground": "btl_b1",
                    "enemyName": "푸른 마수",
                    "enemyAssetKey": "zky_ao",
                    "enemyCns": "zky_ao.cns",
                    "enemyActionName": "할퀴기",
                    "enemyAttackActionId": "primary-command",
                    "enemyAttackFrames": [0, 1, 2, 3],
                },
            )

            skill_action_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            execute_js(port, session_id, battle_skill_action_script(), timeout=3)
            skill_action_state = wait_for_battle_skill_action_state(port, session_id)
            verify_battle_skill_action(
                skill_action_state,
                {
                    "enemyName": "푸른 마수",
                    "enemyHp": 42,
                },
            )

            item_target_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            execute_js(port, session_id, battle_item_target_selection_script(), timeout=3)
            item_target_state = wait_for_battle_item_target_selection_state(port, session_id)
            verify_battle_item_target_selection(item_target_state)

            action_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "20,20"})
            execute_js(port, session_id, start_battle_action_script(), timeout=3)
            action_marker = wait_for_battle_action_marker(port, session_id)
            wait_for_battle_scene(port, session_id)
            action_checksum = int(execute_js(port, session_id, canvas_checksum_script(), timeout=3))
            if action_checksum == 0:
                raise WebDriverError("battle candidate action scene rendered a blank canvas")
            action_state = execute_js(port, session_id, battle_action_state_script(), timeout=3)
            verify_battle_action_state(
                action_state,
                {
                    "map": "map1_02b",
                    "candidateId": "event-dialogue-block-015:btl_b1",
                    "blockId": "event-dialogue-block-015",
                    "battleBackground": "btl_b1",
                    "enemySpriteCandidate": "zky_ao.cns",
                    "enemySpriteAssetKey": "zky_ao",
                    "enemySpriteRefVaHex": "0x00490124",
                    "battleBackgroundRefVaHex": "0x0048f0bc",
                    "enemyName": "푸른 마수",
                    "enemyHp": 42,
                    "enemyAtk": 4,
                    "enemyDef": 0,
                    "enemyActionName": "할퀴기",
                    "enemyActionIndex": 3,
                    "enemyRewardExp": 7,
                    "enemyDropKey": "herb",
                    "enemyDropName": "약초",
                    "enemyDropCount": 1,
                },
            )

            battle_menu_url = load_map(base, port, session_id, {"map": "map2_14j", "startTile": "18,14"})
            execute_js(port, session_id, battle_candidate_menu_selection_script(), timeout=3)
            wait_for_battle_scene(port, session_id)
            battle_menu_state = execute_js(port, session_id, battle_candidate_menu_state_script(), timeout=3)
            verify_battle_candidate_menu_state(battle_menu_state)

            battle_sprite_menu_url = load_map(base, port, session_id, {"map": "map2_14j", "startTile": "18,14"})
            execute_js(port, session_id, battle_sprite_menu_selection_script(), timeout=3)
            wait_for_battle_scene(port, session_id)
            battle_sprite_menu_state = execute_js(port, session_id, battle_sprite_menu_state_script(), timeout=3)
            verify_battle_sprite_menu_state(battle_sprite_menu_state)

            battle_sprite_direct_query = urlencode({
                "map": "map2_14j",
                "startTile": "18,14",
                "battle": "1",
                "battleCandidate": "sprite-only:zk_big",
                "_": str(time.time_ns()),
            })
            battle_sprite_direct_url = urljoin(
                base.rstrip("/") + "/",
                f"/web/game.html?{battle_sprite_direct_query}",
            )
            request_json(port, "POST", f"/session/{session_id}/url", {"url": battle_sprite_direct_url}, timeout=30)
            wait_for_page(port, session_id)
            wait_for_battle_scene(port, session_id)
            battle_sprite_direct_checksum = int(execute_js(port, session_id, canvas_checksum_script(), timeout=3))
            if battle_sprite_direct_checksum == 0:
                raise WebDriverError("direct sprite-only battle URL rendered a blank canvas")
            battle_sprite_direct_state = execute_js(port, session_id, battle_sprite_direct_state_script(), timeout=3)
            verify_battle_sprite_direct_state(battle_sprite_direct_state)

            dialogue_battle_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            execute_js(port, session_id, start_dialogue_battle_link_script(), timeout=3)
            dialogue_battle_state = wait_for_dialogue_battle_link_state(port, session_id)
            dialogue_battle_checksum = int(execute_js(port, session_id, canvas_checksum_script(), timeout=3))
            if dialogue_battle_checksum == 0:
                raise WebDriverError("dialogue battle link scene rendered a blank canvas")
            verify_dialogue_battle_link_state(dialogue_battle_state)

            field_encounter_url = load_map(
                base,
                port,
                session_id,
                {"map": "map1_02b", "startTile": "11,12", "encounter": "1"},
            )
            execute_js(port, session_id, start_field_encounter_script(), timeout=3)
            field_encounter_state = wait_for_field_encounter_state(port, session_id)
            verify_field_encounter_state(field_encounter_state)

            field_encounter_title_url = urljoin(base.rstrip("/") + "/", "/web/game.html?game=1")
            request_json(port, "POST", f"/session/{session_id}/url", {"url": field_encounter_title_url}, timeout=30)
            wait_for_page(port, session_id)
            field_encounter_title_state = wait_for_battle_completion_title_ready(port, session_id)
            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"field encounter title continue button was not usable: {field_encounter_title_click!r}"
                )
            wait_for_map_runtime(port, session_id, "map1_02b")
            execute_js(port, session_id, capture_field_encounter_after_title_continue_script(), timeout=3)
            field_encounter_title_restore_state = wait_for_field_encounter_title_restore_state(port, session_id)
            verify_field_encounter_title_continue_state(
                field_encounter_title_state,
                field_encounter_title_click,
                field_encounter_title_restore_state,
            )

            requested_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map2_07e",
                    "startTile": "11,11",
                    "battleCandidate": "event-dialogue-block-041:btl_e1",
                },
            )
            requested_button, requested_state, requested_checksum = verify_battle_case(
                port,
                session_id,
                {
                    "map": "map2_07e",
                    "candidateId": "event-dialogue-block-041:btl_e1",
                    "blockId": "event-dialogue-block-041",
                    "battleBackground": "btl_e1",
                    "enemySpriteCandidate": "zjk_byk.cns",
                    "enemySpriteAssetKey": "zjk_byk",
                    "enemySpriteRefVaHex": "0x004bdb3c",
                    "battleBackgroundRefVaHex": "0x004bd8b4",
                    "enemyName": "백호 괴수",
                    "enemyHp": 50,
                    "enemyAtk": 5,
                    "enemyDef": 1,
                    "enemyActionName": "돌진",
                    "enemyActionIndex": 8,
                    "enemyRewardExp": 9,
                    "enemyDropKey": "item_2",
                    "enemyDropName": "해독초",
                    "enemyDropCount": 1,
                },
            )

            paralysis_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map1_01a",
                    "startTile": "1,2",
                    "battleCandidate": "event-dialogue-block-041:btl_a1",
                },
            )
            execute_js(port, session_id, start_battle_paralysis_effect_turn_script(), timeout=3)
            paralysis_state = wait_for_battle_paralysis_effect_turn_state(port, session_id)
            verify_battle_paralysis_effect_turn_state(paralysis_state)

            status_cure_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map2_07e",
                    "startTile": "11,11",
                    "battleCandidate": "event-dialogue-block-038:btl_n2",
                },
            )
            execute_js(port, session_id, start_battle_status_cure_script(), timeout=3)
            status_cure_state = wait_for_battle_status_cure_state(port, session_id)
            verify_battle_status_cure_state(status_cure_state)

            status_persist_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map2_07e",
                    "startTile": "11,11",
                    "battleCandidate": "event-dialogue-block-038:btl_n2",
                },
            )
            execute_js(port, session_id, start_battle_status_persist_script(), timeout=3)
            status_persist_state = wait_for_battle_status_persist_state(port, session_id)
            verify_battle_status_persist_state(status_persist_state)

            status_persist_title_url = urljoin(base.rstrip("/") + "/", "/web/game.html?game=1")
            request_json(port, "POST", f"/session/{session_id}/url", {"url": status_persist_title_url}, timeout=30)
            wait_for_page(port, session_id)
            status_persist_title_state = wait_for_title_continue_ready_for_map(
                port,
                session_id,
                "map2_07e",
                "11,11",
            )
            status_persist_title_click = execute_js(port, session_id, click_title_continue_script(), timeout=3)
            if not isinstance(status_persist_title_click, dict) or status_persist_title_click.get("ok") is not True:
                raise WebDriverError(
                    f"candidate battle status title continue button was not usable: {status_persist_title_click!r}"
                )
            wait_for_map_runtime(port, session_id, "map2_07e")
            execute_js(port, session_id, capture_battle_status_persist_after_title_continue_script(), timeout=3)
            status_persist_title_restore_state = wait_for_battle_status_persist_title_restore_state(port, session_id)
            verify_battle_status_persist_title_continue_state(
                status_persist_title_state,
                status_persist_title_click,
                status_persist_title_restore_state,
            )

            status_effect_url = load_map(
                base,
                port,
                session_id,
                {
                    "map": "map2_07e",
                    "startTile": "11,11",
                    "battleCandidate": "event-dialogue-block-038:btl_n2",
                },
            )
            execute_js(port, session_id, start_battle_status_effect_turn_script(), timeout=3)
            status_effect_state = wait_for_battle_status_effect_turn_state(port, session_id)
            verify_battle_status_effect_turn_state(status_effect_state)

            completion_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            execute_js(port, session_id, start_battle_completion_script(), timeout=3)
            completion_state = wait_for_battle_completion_state(port, session_id)
            verify_battle_completion_state(completion_state)

            completion_title_url = urljoin(base.rstrip("/") + "/", "/web/game.html?game=1")
            request_json(port, "POST", f"/session/{session_id}/url", {"url": completion_title_url}, timeout=30)
            wait_for_page(port, session_id)
            completion_title_state = wait_for_battle_completion_title_ready(port, session_id)
            completion_title_click = execute_js(port, session_id, click_title_continue_script(), timeout=3)
            if not isinstance(completion_title_click, dict) or completion_title_click.get("ok") is not True:
                raise WebDriverError(f"candidate battle title continue button was not usable: {completion_title_click!r}")
            wait_for_map_runtime(port, session_id, "map1_02b")
            execute_js(port, session_id, capture_battle_completion_after_title_continue_script(), timeout=3)
            completion_title_restore_state = wait_for_battle_completion_title_restore_state(port, session_id)
            verify_battle_completion_title_continue_state(
                completion_title_state,
                completion_title_click,
                completion_title_restore_state,
            )

            completion_restore_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            execute_js(port, session_id, restore_battle_completion_script(), timeout=3)
            completion_restore_state = wait_for_battle_completion_restore_state(port, session_id)
            verify_battle_completion_state(completion_restore_state, restored=True)

            run_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            execute_js(port, session_id, start_battle_run_script(), timeout=3)
            run_state = wait_for_battle_run_state(port, session_id)
            verify_battle_run_state(run_state)

            run_title_url = urljoin(base.rstrip("/") + "/", "/web/game.html?game=1")
            request_json(port, "POST", f"/session/{session_id}/url", {"url": run_title_url}, timeout=30)
            wait_for_page(port, session_id)
            run_title_state = wait_for_battle_completion_title_ready(port, session_id)
            run_title_click = execute_js(port, session_id, click_title_continue_script(), timeout=3)
            if not isinstance(run_title_click, dict) or run_title_click.get("ok") is not True:
                raise WebDriverError(f"candidate battle run title continue button was not usable: {run_title_click!r}")
            wait_for_map_runtime(port, session_id, "map1_02b")
            execute_js(
                port,
                session_id,
                capture_battle_nonvictory_after_title_continue_script("battle-run"),
                timeout=3,
            )
            run_title_restore_state = wait_for_battle_nonvictory_title_restore_state(
                port,
                session_id,
                "battle-run",
            )
            verify_battle_nonvictory_title_continue_state(
                "battle-run",
                run_title_state,
                run_title_click,
                run_title_restore_state,
            )

            defeat_url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            execute_js(port, session_id, start_battle_defeat_script(), timeout=3)
            defeat_state = wait_for_battle_defeat_state(port, session_id)
            verify_battle_defeat_state(defeat_state)

            defeat_title_url = urljoin(base.rstrip("/") + "/", "/web/game.html?game=1")
            request_json(port, "POST", f"/session/{session_id}/url", {"url": defeat_title_url}, timeout=30)
            wait_for_page(port, session_id)
            defeat_title_state = wait_for_battle_completion_title_ready(port, session_id)
            defeat_title_click = execute_js(port, session_id, click_title_continue_script(), timeout=3)
            if not isinstance(defeat_title_click, dict) or defeat_title_click.get("ok") is not True:
                raise WebDriverError(f"candidate battle defeat title continue button was not usable: {defeat_title_click!r}")
            wait_for_map_runtime(port, session_id, "map1_02b")
            execute_js(
                port,
                session_id,
                capture_battle_nonvictory_after_title_continue_script("battle-defeat"),
                timeout=3,
            )
            defeat_title_restore_state = wait_for_battle_nonvictory_title_restore_state(
                port,
                session_id,
                "battle-defeat",
            )
            verify_battle_nonvictory_title_continue_state(
                "battle-defeat",
                defeat_title_state,
                defeat_title_click,
                defeat_title_restore_state,
            )

            action_start_feedback_last = action_state.get("battleStartFeedbackLast") or {}
            field_start_snapshot = field_encounter_state.get("startSnapshot") or {}
            field_start_feedback_last = field_start_snapshot.get("battleStartFeedbackLast") or {}
            defeat_before_close = defeat_state.get("beforeClose") or {}
            defeat_defend_command = defeat_before_close.get("defendCommand") or {}
            defeat_defend_feedback = defeat_before_close.get("defendFeedbackLast") or {}
            report = {
                "status": "passed",
                "base": base,
                "familyUrl": family_url,
                "skillActionUrl": skill_action_url,
                "itemTargetUrl": item_target_url,
                "actionUrl": action_url,
                "battleMenuUrl": battle_menu_url,
                "dialogueBattleUrl": dialogue_battle_url,
                "fieldEncounterUrl": field_encounter_url,
                "fieldEncounterTitleUrl": field_encounter_title_url,
                "requestedUrl": requested_url,
                "paralysisUrl": paralysis_url,
                "statusCureUrl": status_cure_url,
                "statusPersistUrl": status_persist_url,
                "statusPersistTitleUrl": status_persist_title_url,
                "statusEffectUrl": status_effect_url,
                "completionUrl": completion_url,
                "completionTitleUrl": completion_title_url,
                "completionRestoreUrl": completion_restore_url,
                "runUrl": run_url,
                "runTitleUrl": run_title_url,
                "defeatUrl": defeat_url,
                "defeatTitleUrl": defeat_title_url,
                "mapFamilyBattle": (
                    f"{family_state.get('summary', {}).get('candidateId')} "
                    f"{family_state.get('summary', {}).get('battleBackground')} "
                    f"enemyName={family_state.get('summary', {}).get('enemyName')} "
                    f"enemyHp={family_state.get('summary', {}).get('enemyHp')} "
                    f"enemyAtk={family_state.get('summary', {}).get('enemyAtk')} "
                    f"enemyDef={family_state.get('summary', {}).get('enemyDef')} "
                    f"enemyAction={family_state.get('summary', {}).get('enemyActionName')} "
                    f"enemyActionSource={family_state.get('summary', {}).get('enemyActionSource')} "
                    f"{battle_enemy_action_text_report(family_state.get('summary') or {})} "
                    f"enemyDrop={family_state.get('summary', {}).get('enemyDropName')} "
                    f"enemyDropCount={family_state.get('summary', {}).get('enemyDropCount')} "
                    f"enemyDropItemTextRef={family_state.get('summary', {}).get('enemyDropItemTextTableRefVaHex')} "
                    f"enemyDropItemTextVa={family_state.get('summary', {}).get('enemyDropItemTextTableTextVaHex')} "
                    f"enemySprite={family_state.get('summary', {}).get('enemySpriteCandidate')} "
                    f"enemyAsset={family_state.get('summary', {}).get('enemySpriteAssetKey')} "
                    f"enemyProfileSource={family_state.get('summary', {}).get('enemyProfileSource')} "
                    f"originalDropTableMapped={family_state.get('summary', {}).get('originalDropTableMapped')} "
                    f"originalEnemyRowBound={family_state.get('summary', {}).get('originalEnemyRowBound')} "
                    f"sourceBacked={family_state.get('summary', {}).get('sourceBacked')} "
                    f"{battle_vm_report(family_state.get('summary', {}).get('vmReplay') or {})} "
                    f"checksum={family_checksum}"
                ),
                "pointerBattleCommand": (
                    f"attack hit={pointer_attack_state.get('hitIndex')} "
                    f"hp={pointer_attack_state.get('beforeHp')}->{pointer_attack_state.get('afterHp')} "
                    f"window={pointer_attack_state.get('windowInfo')} "
                    f"hitEffectTargets={','.join(str(effect.get('targetType')) for effect in (pointer_attack_state.get('hitEffectLog') or []))} "
                    f"hitEffectDuration={(pointer_attack_state.get('hitEffect') or {}).get('durationMs')} "
                    f"hitEffectShake={(pointer_attack_state.get('hitEffect') or {}).get('shakePx')} "
                    f"browserHitEffect={(pointer_attack_state.get('hitEffect') or {}).get('browserBattleHitEffectImplemented')} "
                    f"hitEffectSprite={(pointer_attack_state.get('hitEffect') or {}).get('effectAssetKey')} "
                    f"hitEffectSpriteSource={(pointer_attack_state.get('hitEffect') or {}).get('effectSourceCns')} "
                    f"spriteFrame={(pointer_attack_state.get('hitEffectRender') or {}).get('effectSpriteFrameIndex')} "
                    f"spriteDrawn={(pointer_attack_state.get('hitEffectRender') or {}).get('effectSpriteDrawn')} "
                    f"browserHitSprite={(pointer_attack_state.get('hitEffectRender') or {}).get('browserBattleHitSpriteEffectImplemented')} "
                    f"battleHitSoundCount={((pointer_attack_state.get('soundState') or {}).get('counts') or {}).get('battleHit')} "
                    f"battleHitSoundSrc={(((pointer_attack_state.get('soundState') or {}).get('last') or {}).get('src'))} "
                    f"renderTarget={(pointer_attack_state.get('hitEffectRender') or {}).get('targetType')} "
                    f"damageTextTargets={','.join(str(entry.get('targetType')) for entry in (pointer_attack_state.get('damageTextLog') or []))} "
                    f"damageTextDuration={(pointer_attack_state.get('damageText') or {}).get('durationMs')} "
                    f"damageTextRender={','.join(str(entry.get('targetType')) for entry in (pointer_attack_state.get('damageTextRender') or []))} "
                    f"browserDamageText={(pointer_attack_state.get('damageText') or {}).get('browserBattleDamageTextImplemented')} "
                    f"partyActionAnimation={(pointer_attack_state.get('partyActionAnimation') or {}).get('commandKind')} "
                    f"partyActionActor={(pointer_attack_state.get('partyActionAnimation') or {}).get('actorName')} "
                    f"partyActionAsset={(pointer_attack_state.get('partyActionAnimation') or {}).get('assetKey')} "
                    f"partyActionCns={(pointer_attack_state.get('partyActionAnimation') or {}).get('sourceCns')} "
                    f"partyActionFrames={','.join(str(frame) for frame in ((pointer_attack_state.get('partyActionAnimation') or {}).get('frames') or []))} "
                    f"partyActionFrame={(pointer_attack_state.get('partyActionAnimationRender') or {}).get('frameIndex')} "
                    f"partyActionDrawn={(pointer_attack_state.get('partyActionAnimationRender') or {}).get('partyActionSpriteDrawn')} "
                    f"browserPartyActionAnimation={(pointer_attack_state.get('partyActionAnimation') or {}).get('browserBattlePartyActionAnimationImplemented')} "
                    f"prototypePartyActionFrameRuntime={(pointer_attack_state.get('turnResult') or {}).get('prototypePartyActionFrameRuntimeImplemented')} "
                    f"originalPartyActionSequenceBound={(pointer_attack_state.get('partyActionAnimation') or {}).get('originalPartyActionSequenceBound')} "
                    f"enemyAttackAnimation={(pointer_attack_state.get('enemyAttackAnimation') or {}).get('actionId')} "
                    f"enemyAttackAction={(pointer_attack_state.get('enemyAttackAnimation') or {}).get('actionName')} "
                    f"enemyAttackAsset={(pointer_attack_state.get('enemyAttackAnimation') or {}).get('enemyAssetKey')} "
                    f"enemyAttackCns={(pointer_attack_state.get('enemyAttackAnimation') or {}).get('enemyCns')} "
                    f"enemyAttackFrames={','.join(str(frame) for frame in ((pointer_attack_state.get('enemyAttackAnimation') or {}).get('frames') or []))} "
                    f"enemyAttackFrame={(pointer_attack_state.get('enemyAttackAnimationRender') or {}).get('frameIndex')} "
                    f"enemyAttackDrawn={(pointer_attack_state.get('enemyAttackAnimationRender') or {}).get('enemyAttackSpriteDrawn')} "
                    f"browserEnemyAttackAnimation={(pointer_attack_state.get('enemyAttackAnimation') or {}).get('browserBattleEnemyAttackAnimationImplemented')} "
                    f"prototypeMonsterAttackFrameRuntime={(pointer_attack_state.get('turnResult') or {}).get('prototypeMonsterAttackFrameRuntimeImplemented')} "
                    f"originalAttackSequenceBound={(pointer_attack_state.get('enemyAttackAnimation') or {}).get('originalAttackSequenceBound')} "
                    f"originalEnemyAi={(pointer_attack_state.get('enemyAttackAnimation') or {}).get('originalEnemyAiImplemented')} "
                    "canvasPointer=True"
                ),
                "battleSkillAction": (
                    f"skill={(skill_action_state.get('skillUse') or {}).get('skillName')} "
                    f"actor={(skill_action_state.get('skillUse') or {}).get('actorName')} "
                    f"hp={skill_action_state.get('beforeHp')}->{skill_action_state.get('afterHp')} "
                    f"mp={skill_action_state.get('beforeMp')}->{skill_action_state.get('afterMp')} "
                    f"skillBlock={(skill_action_state.get('skillUse') or {}).get('skillBlock')} "
                    f"skillIndex={(skill_action_state.get('skillUse') or {}).get('skillIndex')} "
                    f"skillTextTable={(skill_action_state.get('skillUse') or {}).get('skillTextTableKey')} "
                    f"skillTextIndex={(skill_action_state.get('skillUse') or {}).get('skillTextTableIndex')} "
                    f"skillUseSource={(skill_action_state.get('skillUse') or {}).get('source')} "
                    f"prototypeSkillUse={(skill_action_state.get('skillUse') or {}).get('prototypeSkillUseImplemented')} "
                    f"partyActionAnimation={(skill_action_state.get('partyActionAnimation') or {}).get('commandKind')} "
                    f"partyActionActor={(skill_action_state.get('partyActionAnimation') or {}).get('actorName')} "
                    f"partyActionAsset={(skill_action_state.get('partyActionAnimation') or {}).get('assetKey')} "
                    f"partyActionCns={(skill_action_state.get('partyActionAnimation') or {}).get('sourceCns')} "
                    f"partyActionFrames={','.join(str(frame) for frame in ((skill_action_state.get('partyActionAnimation') or {}).get('frames') or []))} "
                    f"partyActionFrame={(skill_action_state.get('partyActionAnimationRender') or {}).get('frameIndex')} "
                    f"partyActionDrawn={(skill_action_state.get('partyActionAnimationRender') or {}).get('partyActionSpriteDrawn')} "
                    f"browserPartyActionAnimation={(skill_action_state.get('partyActionAnimation') or {}).get('browserBattlePartyActionAnimationImplemented')} "
                    f"prototypePartyActionFrameRuntime={(skill_action_state.get('skillUse') or {}).get('prototypePartyActionFrameRuntimeImplemented')} "
                    f"turnPrototypePartyActionFrameRuntime={(skill_action_state.get('turnResult') or {}).get('prototypePartyActionFrameRuntimeImplemented')} "
                    f"autoPrototypePartyActionFrameRuntime={(skill_action_state.get('skillAutoSave') or {}).get('prototypePartyActionFrameRuntimeImplemented')} "
                    f"originalPartyActionSequenceBound={(skill_action_state.get('partyActionAnimation') or {}).get('originalPartyActionSequenceBound')} "
                    f"originalSkillFormulaImplemented={(skill_action_state.get('skillUse') or {}).get('originalSkillFormulaImplemented')} "
                    f"partySkillFrameActors={','.join(str(entry.get('memberName')) for entry in (skill_action_state.get('partySkillFrames') or []))} "
                    f"partySkillFrameAssets={','.join(str((entry.get('partyActionAnimation') or {}).get('assetKey')) for entry in (skill_action_state.get('partySkillFrames') or []))} "
                    f"partySkillFrameCns={','.join(str((entry.get('partyActionAnimation') or {}).get('sourceCns')) for entry in (skill_action_state.get('partySkillFrames') or []))} "
                    f"partySkillFrameHeights={','.join(str((entry.get('partyActionAnimation') or {}).get('frameHeight')) for entry in (skill_action_state.get('partySkillFrames') or []))} "
                    f"partySkillFrameDrawn={','.join(str((entry.get('partyActionAnimationRender') or {}).get('partyActionSpriteDrawn')) for entry in (skill_action_state.get('partySkillFrames') or []))} "
                    f"battleHitSoundCount={((skill_action_state.get('soundState') or {}).get('counts') or {}).get('battleHit')} "
                    f"battleHitSoundSrc={(((skill_action_state.get('soundState') or {}).get('last') or {}).get('src'))}"
                ),
                "itemTargetSelection": (
                    f"item={(item_target_state.get('itemUse') or {}).get('itemName')} "
                    f"target={(item_target_state.get('itemUse') or {}).get('targetName')} "
                    f"hp={(item_target_state.get('itemUse') or {}).get('hpBefore')}->"
                    f"{(item_target_state.get('itemUse') or {}).get('hpAfter')} "
                    f"count={(item_target_state.get('itemUse') or {}).get('countBefore')}->"
                    f"{(item_target_state.get('itemUse') or {}).get('countAfter')} "
                    f"targetMenuOpen={(item_target_state.get('openMarker') or {}).get('phase')} "
                    f"selectedPhase={(item_target_state.get('selectedMarker') or {}).get('phase')} "
                    f"targetCount={(item_target_state.get('selectedMarker') or {}).get('targetCount')} "
                    f"choices={','.join(str(choice.get('memberName')) for choice in (item_target_state.get('targetChoices') or []))} "
                    f"itemAutoSource={(item_target_state.get('itemAutoSave') or {}).get('source')} "
                    f"turnAutoSource={(item_target_state.get('turnAutoSave') or {}).get('source')} "
                    f"battleTurnCount={(item_target_state.get('progress') or {}).get('counts', {}).get('battle-turn-prototype')} "
                    f"itemUseCount={(item_target_state.get('progress') or {}).get('counts', {}).get('item-use-prototype')} "
                    f"savedHp={next((row.get('hp') for row in (((item_target_state.get('savedPayload') or {}).get('runtimeState') or {}).get('characters') or []) if row.get('name') == 'Rinshan'), '')} "
                    f"savedHerb={next((row.get('count') for row in (((item_target_state.get('savedPayload') or {}).get('runtimeState') or {}).get('items') or []) if row.get('key') == 'herb'), '')} "
                    f"prototypeTargetSelection={(item_target_state.get('selectedMarker') or {}).get('prototypeBattleItemTargetSelectionImplemented')} "
                    f"objective={(item_target_state.get('objectiveBefore') or {}).get('title')} "
                    f"objectiveDetail={(item_target_state.get('objectiveBefore') or {}).get('detail')} "
                    f"objectiveAction={(item_target_state.get('objectiveAction') or {}).get('action')} "
                    f"objectiveActiveId={(item_target_state.get('objectiveAction') or {}).get('activeId')} "
                    f"itemTargetNoticeFeedback={((item_target_state.get('objectiveItemFeedbackLast') or {}).get('source') or '')}:"
                    f"{((item_target_state.get('objectiveItemFeedbackLast') or {}).get('text') or '')} "
                    f"itemTargetNoticeFeedbackRender={bool(item_target_state.get('objectiveItemFeedbackRender') or [])} "
                    f"itemTargetNoticeSound={((item_target_state.get('objectiveItemFeedbackLast') or {}).get('battleItemSound') or '')}:"
                    f"{((item_target_state.get('objectiveItemFeedbackLast') or {}).get('battleItemSoundSrc') or '')}:"
                    f"{((item_target_state.get('objectiveItemFeedbackLast') or {}).get('battleItemSoundPlayed'))} "
                    "originalItemEffectFormulaImplemented=False"
                ),
                "actionBattle": (
                    f"{(action_state.get('summary') or {}).get('candidateId')} "
                    f"{(action_state.get('summary') or {}).get('battleBackground')} "
                    f"enemyName={(action_state.get('summary') or {}).get('enemyName')} "
                    f"enemyHp={(action_state.get('summary') or {}).get('enemyHp')} "
                    f"enemyAtk={(action_state.get('summary') or {}).get('enemyAtk')} "
                    f"enemyDef={(action_state.get('summary') or {}).get('enemyDef')} "
                    f"enemyAction={(action_state.get('summary') or {}).get('enemyActionName')} "
                    f"enemyActionSource={(action_state.get('summary') or {}).get('enemyActionSource')} "
                    f"{battle_enemy_action_text_report(action_state.get('summary') or {})} "
                    f"enemyDrop={(action_state.get('summary') or {}).get('enemyDropName')} "
                    f"enemyDropCount={(action_state.get('summary') or {}).get('enemyDropCount')} "
                    f"enemyDropItemTextRef={(action_state.get('summary') or {}).get('enemyDropItemTextTableRefVaHex')} "
                    f"enemyDropItemTextVa={(action_state.get('summary') or {}).get('enemyDropItemTextTableTextVaHex')} "
                    f"enemySprite={(action_state.get('summary') or {}).get('enemySpriteCandidate')} "
                    f"enemyAsset={(action_state.get('summary') or {}).get('enemySpriteAssetKey')} "
                    f"enemyProfileSource={(action_state.get('summary') or {}).get('enemyProfileSource')} "
                    f"originalDropTableMapped={(action_state.get('summary') or {}).get('originalDropTableMapped')} "
                    f"originalEnemyRowBound={(action_state.get('summary') or {}).get('originalEnemyRowBound')} "
                    f"prompt={((action_marker.get('promptBefore') or {}).get('text') or '')} "
                    f"actionResult={action_marker.get('actionResult')} "
                    f"battleStartCount={(action_state.get('progress') or {}).get('counts', {}).get('battle-start')} "
                    f"battleStartFeedback={(action_start_feedback_last.get('source') or '')}:"
                    f"{(action_start_feedback_last.get('text') or '')} "
                    f"battleStartFeedbackRender={bool(action_state.get('battleStartFeedbackRender') or [])} "
                    f"battleStartFeedbackDuration={action_start_feedback_last.get('durationMs')} "
                    f"battleStartFeedbackSound={action_start_feedback_last.get('battleStartSound')}:"
                    f"{action_start_feedback_last.get('battleStartSoundSrc')}:"
                    f"{action_start_feedback_last.get('battleStartSoundPlayed')} "
                    f"battleStartSound={(((action_state.get('summary') or {}).get('battleStartSound') or {}).get('key'))} "
                    f"battleStartSoundSrc={(((action_state.get('summary') or {}).get('battleStartSound') or {}).get('src'))} "
                    f"{battle_vm_report((action_state.get('summary') or {}).get('vmReplay') or {})} "
                    f"checksum={action_checksum}"
                ),
                "battleCandidateMenu": (
                    f"menuCount={(battle_menu_state.get('menuMarker') or {}).get('count')} "
                    f"selected={(battle_menu_state.get('selection') or {}).get('candidateId')} "
                    f"battleBackground={(battle_menu_state.get('summary') or {}).get('battleBackground')} "
                    f"enemyName={(battle_menu_state.get('summary') or {}).get('enemyName')} "
                    f"enemySprite={(battle_menu_state.get('summary') or {}).get('enemySpriteCandidate')} "
                    f"enemyAsset={(battle_menu_state.get('summary') or {}).get('enemySpriteAssetKey')} "
                    f"source={(battle_menu_state.get('selection') or {}).get('source')} "
                    f"menuMode={((battle_menu_state.get('marker') or {}).get('before') or {}).get('menuMode')} "
                    f"commandResult={(battle_menu_state.get('marker') or {}).get('commandResult')} "
                    f"afterMenuOpen={(battle_menu_state.get('marker') or {}).get('afterMenuOpen')} "
                    f"battleStartFeedback={((battle_menu_state.get('battleStartFeedbackLast') or {}).get('source') or '')}:"
                    f"{((battle_menu_state.get('battleStartFeedbackLast') or {}).get('text') or '')} "
                    f"battleStartFeedbackRender={bool(battle_menu_state.get('battleStartFeedbackRender') or [])} "
                    f"originalEventDrivenBattleEntry={(battle_menu_state.get('summary') or {}).get('originalEventDrivenBattleEntry')} "
                    f"originalEnemyRowBound={(battle_menu_state.get('summary') or {}).get('originalEnemyRowBound')}"
                ),
                "battleSpriteMenu": (
                    f"menuCount={(battle_sprite_menu_state.get('menuMarker') or {}).get('count')} "
                    f"eventCount={(battle_sprite_menu_state.get('menuMarker') or {}).get('eventCandidateCount')} "
                    f"spriteCount={(battle_sprite_menu_state.get('menuMarker') or {}).get('spriteCandidateCount')} "
                    f"selected={(battle_sprite_menu_state.get('selection') or {}).get('candidateId')} "
                    f"battleBackground={(battle_sprite_menu_state.get('summary') or {}).get('battleBackground')} "
                    f"enemyName={(battle_sprite_menu_state.get('summary') or {}).get('enemyName')} "
                    f"enemySprite={(battle_sprite_menu_state.get('summary') or {}).get('enemySpriteCandidate')} "
                    f"enemyAsset={(battle_sprite_menu_state.get('summary') or {}).get('enemySpriteAssetKey')} "
                    f"enemyProfileSource={(battle_sprite_menu_state.get('summary') or {}).get('enemyProfileSource')} "
                    f"source={(battle_sprite_menu_state.get('selection') or {}).get('source')} "
                    f"spriteOnly={(battle_sprite_menu_state.get('selection') or {}).get('spriteOnly')} "
                    f"sourceBacked={(battle_sprite_menu_state.get('summary') or {}).get('sourceBacked')} "
                    f"selectionKind={(battle_sprite_menu_state.get('summary') or {}).get('battleEnemySpriteSelectionKind')} "
                    f"prototypeSpriteOnlyBattleImplemented={(battle_sprite_menu_state.get('summary') or {}).get('prototypeSpriteOnlyBattleImplemented')} "
                    f"menuMode={((battle_sprite_menu_state.get('marker') or {}).get('before') or {}).get('menuMode')} "
                    f"commandResult={(battle_sprite_menu_state.get('marker') or {}).get('commandResult')} "
                    f"afterMenuOpen={(battle_sprite_menu_state.get('marker') or {}).get('afterMenuOpen')} "
                    f"battleStartFeedback={((battle_sprite_menu_state.get('battleStartFeedbackLast') or {}).get('source') or '')}:"
                    f"{((battle_sprite_menu_state.get('battleStartFeedbackLast') or {}).get('text') or '')} "
                    f"battleStartFeedbackRender={bool(battle_sprite_menu_state.get('battleStartFeedbackRender') or [])} "
                    f"originalEventDrivenBattleEntry={(battle_sprite_menu_state.get('summary') or {}).get('originalEventDrivenBattleEntry')} "
                    f"originalEnemyRowBound={(battle_sprite_menu_state.get('summary') or {}).get('originalEnemyRowBound')}"
                ),
                "battleSpriteDirectUrl": (
                    f"selected={(battle_sprite_direct_state.get('summary') or {}).get('candidateId')} "
                    f"requested={(battle_sprite_direct_state.get('summary') or {}).get('requestedBattleCandidate')} "
                    f"battleBackground={(battle_sprite_direct_state.get('summary') or {}).get('battleBackground')} "
                    f"enemyName={(battle_sprite_direct_state.get('summary') or {}).get('enemyName')} "
                    f"enemySprite={(battle_sprite_direct_state.get('summary') or {}).get('enemySpriteCandidate')} "
                    f"enemyAsset={(battle_sprite_direct_state.get('summary') or {}).get('enemySpriteAssetKey')} "
                    f"enemyProfileSource={(battle_sprite_direct_state.get('summary') or {}).get('enemyProfileSource')} "
                    f"sourceBacked={(battle_sprite_direct_state.get('summary') or {}).get('sourceBacked')} "
                    f"selectionKind={(battle_sprite_direct_state.get('summary') or {}).get('battleEnemySpriteSelectionKind')} "
                    f"prototypeSpriteOnlyBattleImplemented={(battle_sprite_direct_state.get('summary') or {}).get('prototypeSpriteOnlyBattleImplemented')} "
                    f"battleStartFeedback={((battle_sprite_direct_state.get('battleStartFeedbackLast') or {}).get('source') or '')}:"
                    f"{((battle_sprite_direct_state.get('battleStartFeedbackLast') or {}).get('text') or '')} "
                    f"battleStartFeedbackRender={bool(battle_sprite_direct_state.get('battleStartFeedbackRender') or [])} "
                    f"originalEventDrivenBattleEntry={(battle_sprite_direct_state.get('summary') or {}).get('originalEventDrivenBattleEntry')} "
                    f"originalEnemyRowBound={(battle_sprite_direct_state.get('summary') or {}).get('originalEnemyRowBound')} "
                    f"checksum={battle_sprite_direct_checksum}"
                ),
                "dialogueBattleLink": (
                    f"{(dialogue_battle_state.get('link') or {}).get('blockId')} "
                    f"{(dialogue_battle_state.get('link') or {}).get('battleBackground')} "
                    f"linkSource={(dialogue_battle_state.get('link') or {}).get('source')} "
                    f"objective={((dialogue_battle_state.get('objectiveBefore') or {}).get('title') or '')} "
                    f"objectiveNext={((dialogue_battle_state.get('objectiveBefore') or {}).get('nextAction') or '')} "
                    f"prompt={((dialogue_battle_state.get('actionPromptBefore') or {}).get('text') or '')} "
                    f"actionResult={dialogue_battle_state.get('actionResult')} "
                    f"linkAction={((dialogue_battle_state.get('dialogueBattleLinkAction') or {}).get('action') or '')} "
                    f"dialogueCompleteCount={(dialogue_battle_state.get('progress') or {}).get('counts', {}).get('dialogue-complete')} "
                    f"dialogueBattleLinkCount={(dialogue_battle_state.get('progress') or {}).get('counts', {}).get('dialogue-battle-link')} "
                    f"battleStartCount={(dialogue_battle_state.get('progress') or {}).get('counts', {}).get('battle-start')} "
                    f"battleStartFeedback={((dialogue_battle_state.get('battleStartFeedbackLast') or {}).get('source') or '')}:"
                    f"{((dialogue_battle_state.get('battleStartFeedbackLast') or {}).get('text') or '')} "
                    f"battleStartFeedbackRender={bool(dialogue_battle_state.get('battleStartFeedbackRender') or [])} "
                    f"battleStartFeedbackSound={((dialogue_battle_state.get('battleStartFeedbackLast') or {}).get('battleStartSound'))}:"
                    f"{((dialogue_battle_state.get('battleStartFeedbackLast') or {}).get('battleStartSoundSrc'))}:"
                    f"{((dialogue_battle_state.get('battleStartFeedbackLast') or {}).get('battleStartSoundPlayed'))} "
                    f"battleStartSound={(((dialogue_battle_state.get('summary') or {}).get('battleStartSound') or {}).get('key'))} "
                    f"battleStartSoundSrc={(((dialogue_battle_state.get('summary') or {}).get('battleStartSound') or {}).get('src'))} "
                    f"dialogueBattleSummary={(dialogue_battle_state.get('summary') or {}).get('dialogueBattleLink')} "
                    f"prototypeDialogueBattleLinkImplemented={(dialogue_battle_state.get('summary') or {}).get('prototypeDialogueBattleLinkImplemented')} "
                    f"autoSource={(dialogue_battle_state.get('autoSave') or {}).get('source')} "
                    f"autoSaved={(dialogue_battle_state.get('autoSave') or {}).get('saved')} "
                    f"{battle_vm_report((dialogue_battle_state.get('summary') or {}).get('vmReplay') or {})} "
                    f"checksum={dialogue_battle_checksum}"
                ),
                "requestedBattle": (
                    f"{requested_state.get('summary', {}).get('candidateId')} "
                    f"{requested_state.get('summary', {}).get('battleBackground')} "
                    f"enemyName={requested_state.get('summary', {}).get('enemyName')} "
                    f"enemyHp={requested_state.get('summary', {}).get('enemyHp')} "
                    f"enemyAtk={requested_state.get('summary', {}).get('enemyAtk')} "
                    f"enemyDef={requested_state.get('summary', {}).get('enemyDef')} "
                    f"enemyAction={requested_state.get('summary', {}).get('enemyActionName')} "
                    f"enemyActionSource={requested_state.get('summary', {}).get('enemyActionSource')} "
                    f"{battle_enemy_action_text_report(requested_state.get('summary') or {})} "
                    f"enemyDrop={requested_state.get('summary', {}).get('enemyDropName')} "
                    f"enemyDropCount={requested_state.get('summary', {}).get('enemyDropCount')} "
                    f"enemyDropItemTextRef={requested_state.get('summary', {}).get('enemyDropItemTextTableRefVaHex')} "
                    f"enemyDropItemTextVa={requested_state.get('summary', {}).get('enemyDropItemTextTableTextVaHex')} "
                    f"enemySprite={requested_state.get('summary', {}).get('enemySpriteCandidate')} "
                    f"enemyAsset={requested_state.get('summary', {}).get('enemySpriteAssetKey')} "
                    f"enemyProfileSource={requested_state.get('summary', {}).get('enemyProfileSource')} "
                    f"originalDropTableMapped={requested_state.get('summary', {}).get('originalDropTableMapped')} "
                    f"originalEnemyRowBound={requested_state.get('summary', {}).get('originalEnemyRowBound')} "
                    f"requested={requested_state.get('summary', {}).get('requestedBattleCandidate')} "
                    f"{battle_vm_report(requested_state.get('summary', {}).get('vmReplay') or {})} "
                    f"checksum={requested_checksum}"
                ),
                "fieldEncounter": (
                    f"{((field_encounter_state.get('startSnapshot') or {}).get('summary') or {}).get('candidateId')} "
                    f"{((field_encounter_state.get('startSnapshot') or {}).get('summary') or {}).get('battleBackground')} "
                    f"started={field_encounter_state.get('started')} "
                    f"startScene={((field_encounter_state.get('startSnapshot') or {}).get('scene') or '')} "
                    f"scene={field_encounter_state.get('scene')} "
                    f"movementInput={field_encounter_state.get('movementInput')} "
                    f"movementSteps={len(field_encounter_state.get('movementSteps') or [])} "
                    f"movementCodes={','.join(str(step.get('code')) for step in (field_encounter_state.get('movementSteps') or []))} "
                    f"steps={((field_encounter_state.get('marker') or {}).get('steps'))}/"
                    f"{((field_encounter_state.get('marker') or {}).get('threshold'))} "
                    f"encounterFeedback={((field_encounter_state.get('encounterFeedbackLast') or {}).get('source') or '')}:"
                    f"{((field_encounter_state.get('encounterFeedbackLast') or {}).get('text') or '')} "
                    f"encounterFeedbackRender={bool(field_encounter_state.get('encounterFeedbackRender') or [])} "
                    f"encounterFeedbackSound={((field_encounter_state.get('encounterFeedbackLast') or {}).get('fieldEncounterSound'))}:"
                    f"{((field_encounter_state.get('encounterFeedbackLast') or {}).get('fieldEncounterSoundSrc'))}:"
                    f"{((field_encounter_state.get('encounterFeedbackLast') or {}).get('fieldEncounterSoundPlayed'))} "
                    f"battleStartFeedback={(field_start_feedback_last.get('source') or '')}:"
                    f"{(field_start_feedback_last.get('text') or '')} "
                    f"battleStartFeedbackRender={bool(field_start_snapshot.get('battleStartFeedbackRender') or [])} "
                    f"battleStartFeedbackSound={field_start_feedback_last.get('battleStartSound')}:"
                    f"{field_start_feedback_last.get('battleStartSoundSrc')}:"
                    f"{field_start_feedback_last.get('battleStartSoundPlayed')} "
                    f"battleStartSound={((((field_encounter_state.get('startSnapshot') or {}).get('summary') or {}).get('battleStartSound') or {}).get('key'))} "
                    f"battleStartSoundSrc={((((field_encounter_state.get('startSnapshot') or {}).get('summary') or {}).get('battleStartSound') or {}).get('src'))} "
                    f"battleMusicCue={(((field_encounter_state.get('startSnapshot') or {}).get('musicCue') or {}).get('cue'))} "
                    f"battleMusicSrc={(((field_encounter_state.get('startSnapshot') or {}).get('musicCue') or {}).get('src'))} "
                    f"mapMusicCue={((field_encounter_state.get('musicCue') or {}).get('cue'))} "
                    f"mapMusicSrc={((field_encounter_state.get('musicCue') or {}).get('src'))} "
                    f"battleEndMusicReason={((field_encounter_state.get('battleEndMusicCue') or {}).get('reason'))} "
                    f"musicStop={((field_encounter_state.get('musicSynthStop') or {}).get('previousCue'))}->"
                    f"{((field_encounter_state.get('musicSynthStop') or {}).get('nextCue'))} "
                    f"musicStopReason={((field_encounter_state.get('musicSynthStop') or {}).get('reason'))} "
                    f"musicStopImplemented={((field_encounter_state.get('musicSynthStop') or {}).get('browserMidiSynthStopImplemented'))} "
                    f"fieldEncounterCount={(field_encounter_state.get('progress') or {}).get('counts', {}).get('field-encounter')} "
                    f"fieldEncounterVictoryCount={(field_encounter_state.get('progress') or {}).get('counts', {}).get('field-encounter-victory')} "
                    f"battleVictoryCount={(field_encounter_state.get('progress') or {}).get('counts', {}).get('battle-victory', 0)} "
                    f"battleStartCount={(field_encounter_state.get('progress') or {}).get('counts', {}).get('battle-start')} "
                    f"repeatable={(((field_encounter_state.get('startSnapshot') or {}).get('summary') or {}).get('repeatableFieldEncounter'))} "
                    f"button={field_encounter_state.get('buttonText')} "
                    f"completionCompleted={((field_encounter_state.get('completion') or {}).get('battle') or {}).get('completed')} "
                    f"fieldCompletionCompleted={((field_encounter_state.get('completion') or {}).get('fieldEncounter') or {}).get('completed')} "
                    f"objective={((field_encounter_state.get('objectiveBefore') or {}).get('title') or '')} "
                    f"objectiveNext={((field_encounter_state.get('objectiveBefore') or {}).get('nextAction') or '')} "
                    f"objectiveAction={((field_encounter_state.get('objectiveAction') or {}).get('action') or '')} "
                    f"objectiveActiveId={((field_encounter_state.get('objectiveAction') or {}).get('activeId') or '')} "
                    f"objectiveNotice={((field_encounter_state.get('objectiveCompletionNotice') or {}).get('blockId') or '')} "
                    f"{field_encounter_completion_notice_feedback_summary(field_encounter_state)} "
                    f"drop={(((field_encounter_state.get('victorySummary') or {}).get('itemDrop') or {}).get('itemName'))} "
                    f"dropCount={(((field_encounter_state.get('victorySummary') or {}).get('itemDrop') or {}).get('countAfter'))} "
                    f"dropSource={(((field_encounter_state.get('victorySummary') or {}).get('itemDrop') or {}).get('source'))} "
                    f"dropItemTextRef={(((field_encounter_state.get('victorySummary') or {}).get('itemDrop') or {}).get('itemTextTableRefVaHex'))} "
                    f"dropItemTextVa={(((field_encounter_state.get('victorySummary') or {}).get('itemDrop') or {}).get('itemTextTableTextVaHex'))} "
                    f"victorySound={(((field_encounter_state.get('victorySummary') or {}).get('victorySound') or {}).get('key'))} "
                    f"victorySoundSrc={(((field_encounter_state.get('victorySummary') or {}).get('victorySound') or {}).get('src'))} "
                    f"runtimeMoney={(field_encounter_state.get('runtimeState') or {}).get('money')} "
                    f"autoSaved={((field_encounter_state.get('victoryAutoSave') or {}).get('saved'))} "
                    f"saveMap={((field_encounter_state.get('savedPayload') or {}).get('map'))} "
                    f"saveTile={(((field_encounter_state.get('savedPayload') or {}).get('tile') or {}).get('x'))},"
                    f"{(((field_encounter_state.get('savedPayload') or {}).get('tile') or {}).get('y'))} "
                    "loadedSaveSummary=False "
                    f"source={((field_encounter_state.get('marker') or {}).get('source') or '')} "
                    f"originalEncounterTableMapped={((field_encounter_state.get('marker') or {}).get('originalEncounterTableMapped'))} "
                    f"originalEncounterRuntimeImplemented={((field_encounter_state.get('marker') or {}).get('originalEncounterRuntimeImplemented'))}"
                ),
                "fieldEncounterTitleContinue": (
                    "titleContinue=True "
                    f"label={next((label for label in (field_encounter_title_state.get('titleMenuLabels') or []) if '이어하기 map1_02b 11,12' in str(label)), '')} "
                    f"map={field_encounter_title_restore_state.get('map')} "
                    f"search={field_encounter_title_restore_state.get('search')} "
                    f"encounter={(field_encounter_title_restore_state.get('progress') or {}).get('counts', {}).get('field-encounter')} "
                    f"button={field_encounter_title_restore_state.get('buttonText')} "
                    f"fieldStep={((field_encounter_title_restore_state.get('fieldEncounter') or {}).get('stepCount'))}/6 "
                    f"fieldEncounterCount={(field_encounter_title_restore_state.get('progress') or {}).get('counts', {}).get('field-encounter')} "
                    f"fieldEncounterVictoryCount={(field_encounter_title_restore_state.get('progress') or {}).get('counts', {}).get('field-encounter-victory')} "
                    f"battleVictoryCount={(field_encounter_title_restore_state.get('progress') or {}).get('counts', {}).get('battle-victory', 0)} "
                    f"completionCompleted={((field_encounter_title_restore_state.get('completion') or {}).get('battle') or {}).get('completed')} "
                    f"fieldCompletionCompleted={((field_encounter_title_restore_state.get('completion') or {}).get('fieldEncounter') or {}).get('completed')} "
                    f"objective={((field_encounter_title_restore_state.get('objectiveBefore') or {}).get('title') or '')} "
                    f"objectiveNext={((field_encounter_title_restore_state.get('objectiveBefore') or {}).get('nextAction') or '')} "
                    f"objectiveAction={((field_encounter_title_restore_state.get('objectiveAction') or {}).get('action') or '')} "
                    f"objectiveActiveId={((field_encounter_title_restore_state.get('objectiveAction') or {}).get('activeId') or '')} "
                    f"objectiveNotice={((field_encounter_title_restore_state.get('objectiveCompletionNotice') or {}).get('blockId') or '')} "
                    f"{field_encounter_completion_notice_feedback_summary(field_encounter_title_restore_state)} "
                    f"drop={next((item.get('name') for item in ((field_encounter_title_restore_state.get('runtimeState') or {}).get('items') or []) if item.get('key') == 'herb'), '')} "
                    f"dropCount={next((item.get('count') for item in ((field_encounter_title_restore_state.get('runtimeState') or {}).get('items') or []) if item.get('key') == 'herb'), '')} "
                    f"runtimeMoney={(field_encounter_title_restore_state.get('runtimeState') or {}).get('money')} "
                    f"quickLoadText={field_encounter_title_restore_state.get('quickLoadText')} "
                    "loadedSaveSummary=False"
                ),
                "statusCure": (
                    f"{(status_cure_state.get('summary') or {}).get('candidateId')} "
                    f"{(status_cure_state.get('summary') or {}).get('battleBackground')} "
                    f"enemyName={(status_cure_state.get('summary') or {}).get('enemyName')} "
                    f"enemyAction={(status_cure_state.get('summary') or {}).get('enemyActionName')} "
                    f"{battle_enemy_action_text_report(status_cure_state.get('summary') or {})} "
                    f"status={(status_cure_state.get('summary') or {}).get('enemyStatusName')} "
                    f"poisoned={(status_cure_state.get('afterPoison') or {}).get('name')} "
                    f"poisonBefore={'poison' in ((status_cure_state.get('afterPoison') or {}).get('statuses') or [])} "
                    f"item={((status_cure_state.get('itemCommand') or {}).get('name') or '')} "
                    f"poisonAfter={'poison' in ((status_cure_state.get('afterCure') or {}).get('statuses') or [])} "
                    f"itemCountAfter={status_cure_state.get('itemCountAfter')} "
	                    f"enemyStatusInflicted={((status_cure_state.get('enemyStatusInflicted') or {}).get('poison'))} "
	                    f"itemTextRef={(status_cure_state.get('itemUse') or {}).get('itemTextTableRefVaHex')} "
	                    f"itemTextVa={(status_cure_state.get('itemUse') or {}).get('itemTextTableTextVaHex')} "
	                    f"hpFailFeedback={battle_item_feedback_summary(status_cure_state.get('noTargetFailure') or {})} "
	                    f"hpFailFeedbackRender={battle_item_feedback_rendered(status_cure_state.get('noTargetFailure') or {})} "
	                    f"hpFailSoundItemCount={((((status_cure_state.get('noTargetFailure') or {}).get('soundState') or {}).get('counts') or {}).get('item'))} "
	                    f"itemFailFeedback={battle_item_feedback_summary(status_cure_state.get('noStatusFailure') or {})} "
	                    f"itemFailFeedbackRender={battle_item_feedback_rendered(status_cure_state.get('noStatusFailure') or {})} "
	                    f"itemFailSoundItemCount={((((status_cure_state.get('noStatusFailure') or {}).get('soundState') or {}).get('counts') or {}).get('item'))} "
	                    f"itemFeedback={battle_item_feedback_summary(status_cure_state)} "
	                    f"itemFeedbackRender={battle_item_feedback_rendered(status_cure_state)} "
                    f"itemFeedbackDuration={battle_item_feedback_duration(status_cure_state)} "
                    f"originalStatusFormulaImplemented={status_cure_state.get('originalStatusFormulaImplemented')} "
                    f"originalEnemyAiImplemented={status_cure_state.get('originalEnemyAiImplemented')}"
                ),
                "statusPersist": (
                    f"{(status_persist_state.get('summary') or {}).get('candidateId')} "
                    f"{(status_persist_state.get('summary') or {}).get('battleBackground')} "
                    f"enemyName={(status_persist_state.get('summary') or {}).get('enemyName')} "
                    f"enemyAction={(status_persist_state.get('summary') or {}).get('enemyActionName')} "
                    f"{battle_enemy_action_text_report(status_persist_state.get('summary') or {})} "
                    f"status={(status_persist_state.get('summary') or {}).get('enemyStatusName')} "
                    f"poisoned={(status_persist_state.get('afterPoison') or {}).get('name')} "
                    f"poisonAfterAttack={'poison' in ((status_persist_state.get('afterPoison') or {}).get('statuses') or [])} "
                    f"statusApplied={(status_persist_state.get('turnResult') or {}).get('statusApplied')} "
                    f"autoSaved={(status_persist_state.get('autoSave') or {}).get('saved')} "
                    f"autoSource={(status_persist_state.get('autoSave') or {}).get('source')} "
                    f"saveMap={(status_persist_state.get('savedPayload') or {}).get('map')} "
                    f"battleTurnCount={(status_persist_state.get('progress') or {}).get('counts', {}).get('battle-turn-prototype')} "
                    f"battleVictoryCount={(status_persist_state.get('progress') or {}).get('counts', {}).get('battle-victory', 0)} "
                    f"savedPoison={'poison' in ((status_persist_state.get('savedCharacter') or {}).get('statuses') or [])} "
                    f"enemyStatusInflicted={((status_persist_state.get('enemyStatusInflicted') or {}).get('poison'))} "
                    f"statusFeedback={battle_status_feedback_sources(status_persist_state)} "
                    f"statusFeedbackRender={battle_status_feedback_rendered(status_persist_state)} "
                    f"statusFeedbackDuration={battle_status_feedback_duration(status_persist_state)} "
                    f"originalStatusFormulaImplemented={status_persist_state.get('originalStatusFormulaImplemented')} "
                    f"originalEnemyAiImplemented={status_persist_state.get('originalEnemyAiImplemented')}"
                ),
                "statusPersistTitleContinue": (
                    "titleContinue=True "
                    f"label={next((label for label in (status_persist_title_state.get('titleMenuLabels') or []) if '이어하기 map2_07e 11,11' in str(label)), '')} "
                    f"map={status_persist_title_restore_state.get('map')} "
                    f"search={status_persist_title_restore_state.get('search')} "
                    f"poisoned={(status_persist_title_restore_state.get('poisoned') or {}).get('name')} "
                    f"poisonRestored={'poison' in ((status_persist_title_restore_state.get('poisoned') or {}).get('statuses') or [])} "
                    f"statusBlock={((status_persist_title_restore_state.get('statusReview') or {}).get('blockId') or '')} "
                    f"statusLineHasPoison={'상태 독' in ' '.join(str(line) for line in ((status_persist_title_restore_state.get('statusReview') or {}).get('lines') or []))} "
                    f"statusReview={'|'.join(str(line) for line in ((status_persist_title_restore_state.get('statusReview') or {}).get('lines') or []))} "
                    f"battleTurnCount={(status_persist_title_restore_state.get('progress') or {}).get('counts', {}).get('battle-turn-prototype')} "
                    f"objective={(status_persist_title_restore_state.get('objectiveBefore') or {}).get('title')} "
                    f"objectiveAction={(status_persist_title_restore_state.get('objectiveAction') or {}).get('action')} "
                    f"objectiveActiveId={(status_persist_title_restore_state.get('objectiveAction') or {}).get('activeId')} "
                    f"battleVictoryCount={(status_persist_title_restore_state.get('progress') or {}).get('counts', {}).get('battle-victory', 0)} "
                    f"button={status_persist_title_restore_state.get('buttonText')} "
                    f"quickLoadText={status_persist_title_restore_state.get('quickLoadText')} "
                    f"turnNoticeFeedback={((status_persist_title_restore_state.get('battleTurnCompletionFeedbackLast') or {}).get('source') or '')}:"
                    f"{((status_persist_title_restore_state.get('battleTurnCompletionFeedbackLast') or {}).get('text') or '')} "
                    f"turnNoticeFeedbackRender={bool(status_persist_title_restore_state.get('battleTurnCompletionFeedbackRender') or [])} "
                    f"turnNoticeSound={((status_persist_title_restore_state.get('battleTurnCompletionFeedbackLast') or {}).get('battleCompletionSound') or '')}:"
                    f"{((status_persist_title_restore_state.get('battleTurnCompletionFeedbackLast') or {}).get('battleCompletionSoundSrc') or '')}:"
                    f"{((status_persist_title_restore_state.get('battleTurnCompletionFeedbackLast') or {}).get('battleCompletionSoundPlayed'))} "
                    "loadedSaveSummary=False "
                    f"originalStatusFormulaImplemented={status_persist_title_restore_state.get('originalStatusFormulaImplemented')} "
                    f"originalEnemyAiImplemented={status_persist_title_restore_state.get('originalEnemyAiImplemented')}"
                ),
                "statusEffectTurn": (
                    f"{(status_effect_state.get('summary') or {}).get('candidateId')} "
                    f"{(status_effect_state.get('summary') or {}).get('battleBackground')} "
                    f"enemyName={(status_effect_state.get('summary') or {}).get('enemyName')} "
                    f"enemyAction={(status_effect_state.get('summary') or {}).get('enemyActionName')} "
                    f"{battle_enemy_action_text_report(status_effect_state.get('summary') or {})} "
                    f"effectSource={(status_effect_state.get('statusEffect') or {}).get('source')} "
                    f"effectKind={(((status_effect_state.get('statusEffect') or {}).get('effects') or [{}])[0]).get('key')} "
                    f"poisonDamage={(status_effect_state.get('statusEffect') or {}).get('damageTotal')} "
                    f"poisonHp={(((status_effect_state.get('statusEffect') or {}).get('effects') or [{}])[0]).get('hpBefore')}->"
                    f"{(((status_effect_state.get('statusEffect') or {}).get('effects') or [{}])[0]).get('hpAfter')} "
                    f"turnHp={(status_effect_state.get('secondTurnResult') or {}).get('targetHpBefore')}->"
                    f"{(status_effect_state.get('secondTurnResult') or {}).get('targetHpAfter')} "
                    f"statusEffectDamage={(status_effect_state.get('secondTurnResult') or {}).get('statusEffectDamage')} "
                    f"enemyAttackAnimation={(status_effect_state.get('firstEnemyAttackAnimation') or {}).get('actionId')} "
                    f"enemyAttackAction={(status_effect_state.get('firstEnemyAttackAnimation') or {}).get('actionName')} "
                    f"enemyAttackAsset={(status_effect_state.get('firstEnemyAttackAnimation') or {}).get('enemyAssetKey')} "
                    f"enemyAttackCns={(status_effect_state.get('firstEnemyAttackAnimation') or {}).get('enemyCns')} "
                    f"enemyAttackFrames={','.join(str(frame) for frame in ((status_effect_state.get('firstEnemyAttackAnimation') or {}).get('frames') or []))} "
                    f"enemyAttackFrame={(status_effect_state.get('firstEnemyAttackAnimationRender') or {}).get('frameIndex')} "
                    f"enemyAttackDrawn={(status_effect_state.get('firstEnemyAttackAnimationRender') or {}).get('enemyAttackSpriteDrawn')} "
                    f"secondEnemyAttackAnimation={(status_effect_state.get('secondEnemyAttackAnimation') or {}).get('actionId')} "
                    f"secondEnemyAttackDrawn={(status_effect_state.get('secondEnemyAttackAnimationRender') or {}).get('enemyAttackSpriteDrawn')} "
                    f"battleStatusCount={(status_effect_state.get('progress') or {}).get('counts', {}).get('battle-status-effect-prototype')} "
                    f"battleTurnCount={(status_effect_state.get('progress') or {}).get('counts', {}).get('battle-turn-prototype')} "
                    f"autoSaved={(status_effect_state.get('autoSave') or {}).get('saved')} "
                    f"autoSource={(status_effect_state.get('autoSave') or {}).get('source')} "
                    f"savedHp={(status_effect_state.get('savedCharacter') or {}).get('hp')} "
                    f"savedPoison={'poison' in ((status_effect_state.get('savedCharacter') or {}).get('statuses') or [])} "
                    f"statusFeedbackSources={battle_status_feedback_sources(status_effect_state)} "
                    f"statusFeedbackTexts={battle_status_feedback_texts(status_effect_state)} "
                    f"statusFeedbackSound={battle_status_feedback_sound_summary(status_effect_state)} "
                    f"statusFeedbackRender={battle_status_feedback_rendered(status_effect_state)} "
                    f"statusFeedbackDuration={battle_status_feedback_duration(status_effect_state)} "
                    f"originalStatusFormulaImplemented={status_effect_state.get('originalStatusFormulaImplemented')} "
                    f"originalCombatFormulaImplemented={status_effect_state.get('originalCombatFormulaImplemented')}"
                ),
                "paralysisTurn": (
                    f"{(paralysis_state.get('summary') or {}).get('candidateId')} "
                    f"{(paralysis_state.get('summary') or {}).get('battleBackground')} "
                    f"enemyName={(paralysis_state.get('summary') or {}).get('enemyName')} "
                    f"status={(paralysis_state.get('summary') or {}).get('enemyStatusName')} "
                    f"effectSource={(paralysis_state.get('statusEffect') or {}).get('source')} "
                    f"effectKind={(((paralysis_state.get('statusEffect') or {}).get('effects') or [{}])[0]).get('key')} "
                    f"skipped={(paralysis_state.get('statusEffect') or {}).get('skipped')} "
                    f"enemyHp={paralysis_state.get('enemyHpBeforeSecondCommand')}->"
                    f"{paralysis_state.get('enemyHpAfterSecondCommand')} "
                    f"turnHp={(paralysis_state.get('secondTurnResult') or {}).get('targetHpBefore')}->"
                    f"{(paralysis_state.get('secondTurnResult') or {}).get('targetHpAfter')} "
                    f"commandKey={(paralysis_state.get('secondTurnResult') or {}).get('commandKey')} "
                    f"statusEffectSkipped={(paralysis_state.get('secondTurnResult') or {}).get('statusEffectSkipped')} "
                    f"battleStatusCount={(paralysis_state.get('progress') or {}).get('counts', {}).get('battle-status-effect-prototype')} "
                    f"battleTurnCount={(paralysis_state.get('progress') or {}).get('counts', {}).get('battle-turn-prototype')} "
                    f"autoSaved={(paralysis_state.get('autoSave') or {}).get('saved')} "
                    f"autoSource={(paralysis_state.get('autoSave') or {}).get('source')} "
                    f"savedHp={(paralysis_state.get('savedCharacter') or {}).get('hp')} "
                    f"savedParalysis={'paralysis' in ((paralysis_state.get('savedCharacter') or {}).get('statuses') or [])} "
                    f"statusFeedbackSources={battle_status_feedback_sources(paralysis_state)} "
                    f"statusFeedbackTexts={battle_status_feedback_texts(paralysis_state)} "
                    f"statusFeedbackSound={battle_status_feedback_sound_summary(paralysis_state)} "
                    f"statusFeedbackRender={battle_status_feedback_rendered(paralysis_state)} "
                    f"statusFeedbackDuration={battle_status_feedback_duration(paralysis_state)} "
                    f"originalStatusFormulaImplemented={paralysis_state.get('originalStatusFormulaImplemented')} "
                    f"originalCombatFormulaImplemented={paralysis_state.get('originalCombatFormulaImplemented')}"
                ),
                "completedBattle": (
                    f"{(completion_restore_state.get('completion') or {}).get('battle', {}).get('id')} "
                    f"{(completion_restore_state.get('completion') or {}).get('battle', {}).get('battleBackground')} "
                    f"button={completion_restore_state.get('buttonText')} "
                    f"prompt={((completion_restore_state.get('actionPrompt') or {}).get('text') or '')} "
                    f"notice={((completion_restore_state.get('completionNotice') or {}).get('blockId') or '')} "
                    f"completedActionResult={completion_restore_state.get('completedActionResult')} "
                    f"objective={((completion_restore_state.get('objectiveBefore') or {}).get('title') or '')} "
                    f"objectiveNext={((completion_restore_state.get('objectiveBefore') or {}).get('nextAction') or '')} "
                    f"objectiveAction={((completion_restore_state.get('objectiveAction') or {}).get('action') or '')} "
                    f"objectiveActiveId={((completion_restore_state.get('objectiveAction') or {}).get('activeId') or '')} "
                    f"menu={completion_restore_state.get('menuBattleName')} "
                    f"menuNotice={((completion_restore_state.get('menuCompletionNotice') or {}).get('blockId') or '')} "
                    f"completionFeedbackCount={len(completion_restore_state.get('battleCompletionFeedbackLog') or [])} "
                    f"completionFeedback={((completion_restore_state.get('battleCompletionFeedbackLast') or {}).get('source') or '')}:"
                    f"{((completion_restore_state.get('battleCompletionFeedbackLast') or {}).get('text') or '')} "
                    f"completionFeedbackRender={bool(completion_restore_state.get('battleCompletionFeedbackRender') or [])} "
                    f"completionFeedbackSound={((completion_restore_state.get('battleCompletionFeedbackLast') or {}).get('battleCompletionSound') or '')}:"
                    f"{((completion_restore_state.get('battleCompletionFeedbackLast') or {}).get('battleCompletionSoundSrc') or '')}:"
                    f"{((completion_restore_state.get('battleCompletionFeedbackLast') or {}).get('battleCompletionSoundPlayed'))} "
                    f"noticeLines={'|'.join(str(line) for line in ((completion_restore_state.get('completionNotice') or {}).get('lines') or []))} "
                    f"progressReview={'|'.join(str(line) for line in ((completion_restore_state.get('progressReviewBlock') or {}).get('lines') or []))} "
                    f"battleVictoryCount={(completion_restore_state.get('progress') or {}).get('counts', {}).get('battle-victory')} "
                    f"{story_flag_report((completion_state.get('victoryAutoSave') or {}).get('storyFlags'), 'battleVictory')} "
                    f"{story_flag_report((completion_state.get('savedPayload') or {}).get('prototypeStoryFlags'), 'savedBattleVictory')} "
                    f"rewardExp={(((completion_state.get('victorySummary') or {}).get('progressEvent') or {}).get('detail') or {}).get('exp')} "
                    f"rewardEffectSource={(completion_state.get('rewardEffect') or {}).get('source')} "
                    f"rewardEffectDuration={(completion_state.get('rewardEffect') or {}).get('durationMs')} "
                    f"rewardEffectLines={','.join(str(line) for line in ((completion_state.get('rewardEffect') or {}).get('lines') or []))} "
                    f"rewardEffectRender={(completion_state.get('rewardEffectRender') or {}).get('browserBattleRewardFeedbackImplemented')} "
                    f"rewardEffectRenderLines={','.join(str(line) for line in ((completion_state.get('rewardEffectRender') or {}).get('lines') or []))} "
                    f"rewardEffectSound={((completion_state.get('rewardEffect') or {}).get('battleRewardSound'))}:"
                    f"{((completion_state.get('rewardEffect') or {}).get('battleRewardSoundSrc'))}:"
                    f"{((completion_state.get('rewardEffect') or {}).get('battleRewardSoundPlayed'))} "
                    f"rewardDropItemTextRef={((completion_state.get('rewardEffect') or {}).get('itemDrop') or {}).get('itemTextTableRefVaHex')} "
                    f"rewardRenderDropItemTextRef={(completion_state.get('rewardEffectRender') or {}).get('itemTextTableRefVaHex')} "
                    f"{battle_vm_report((completion_state.get('victorySummary') or {}).get('vmReplay') or {})} "
                    f"drop={(((completion_state.get('victorySummary') or {}).get('itemDrop') or {}).get('itemName'))} "
                    f"dropCount={(((completion_state.get('victorySummary') or {}).get('itemDrop') or {}).get('countAfter'))} "
                    f"dropSource={(((completion_state.get('victorySummary') or {}).get('itemDrop') or {}).get('source'))} "
                    f"dropItemTextRef={(((completion_state.get('victorySummary') or {}).get('itemDrop') or {}).get('itemTextTableRefVaHex'))} "
                    f"dropItemTextVa={(((completion_state.get('victorySummary') or {}).get('itemDrop') or {}).get('itemTextTableTextVaHex'))} "
                    f"victorySound={(((completion_state.get('victorySummary') or {}).get('victorySound') or {}).get('key'))} "
                    f"victorySoundSrc={(((completion_state.get('victorySummary') or {}).get('victorySound') or {}).get('src'))} "
                    f"expTotal={(completion_state.get('runtimeState') or {}).get('expTotal')} "
                    f"levelUpCount={len((completion_state.get('victorySummary') or {}).get('levelUps') or [])} "
                    f"levelAfter={(((completion_state.get('runtimeState') or {}).get('characterExp') or [{}])[0]).get('level')} "
                    f"nextExp={(((completion_state.get('runtimeState') or {}).get('characterExp') or [{}])[0]).get('exp')}/"
                    f"{(((completion_state.get('runtimeState') or {}).get('characterExp') or [{}])[0]).get('expMax')} "
                    f"runtimeMoney={(completion_state.get('runtimeState') or {}).get('money')} "
                    f"autoSaved={((completion_state.get('victoryAutoSave') or {}).get('saved'))} "
                    f"autoSource={((completion_state.get('victoryAutoSave') or {}).get('source'))} "
                    f"saveMap={((completion_state.get('savedPayload') or {}).get('map'))} "
                    f"saveTile={(((completion_state.get('savedPayload') or {}).get('tile') or {}).get('x'))},"
                    f"{(((completion_state.get('savedPayload') or {}).get('tile') or {}).get('y'))} "
                    "loadedSaveSummary=False "
                    f"progressRestored={completion_restore_state.get('loaded')} "
                    "originalStoryFlagRuntimeImplemented=False"
                ),
                "completionTitleContinue": (
                    f"{(completion_title_restore_state.get('completion') or {}).get('battle', {}).get('id')} "
                    f"{(completion_title_restore_state.get('completion') or {}).get('battle', {}).get('battleBackground')} "
                    "titleContinue=True "
                    f"label={next((label for label in (completion_title_state.get('titleMenuLabels') or []) if '이어하기 map1_02b 11,12' in str(label)), '')} "
                    f"button={completion_title_restore_state.get('buttonText')} "
                    f"prompt={((completion_title_restore_state.get('actionPrompt') or {}).get('text') or '')} "
                    f"notice={((completion_title_restore_state.get('completionNotice') or {}).get('blockId') or '')} "
                    f"completedActionResult={completion_title_restore_state.get('completedActionResult')} "
                    f"objective={((completion_title_restore_state.get('objectiveBefore') or {}).get('title') or '')} "
                    f"objectiveNext={((completion_title_restore_state.get('objectiveBefore') or {}).get('nextAction') or '')} "
                    f"objectiveAction={((completion_title_restore_state.get('objectiveAction') or {}).get('action') or '')} "
                    f"objectiveActiveId={((completion_title_restore_state.get('objectiveAction') or {}).get('activeId') or '')} "
                    f"menu={completion_title_restore_state.get('menuBattleName')} "
                    f"menuNotice={((completion_title_restore_state.get('menuCompletionNotice') or {}).get('blockId') or '')} "
                    f"completionFeedbackCount={len(completion_title_restore_state.get('battleCompletionFeedbackLog') or [])} "
                    f"completionFeedback={((completion_title_restore_state.get('battleCompletionFeedbackLast') or {}).get('source') or '')}:"
                    f"{((completion_title_restore_state.get('battleCompletionFeedbackLast') or {}).get('text') or '')} "
                    f"completionFeedbackRender={bool(completion_title_restore_state.get('battleCompletionFeedbackRender') or [])} "
                    f"completionFeedbackSound={((completion_title_restore_state.get('battleCompletionFeedbackLast') or {}).get('battleCompletionSound') or '')}:"
                    f"{((completion_title_restore_state.get('battleCompletionFeedbackLast') or {}).get('battleCompletionSoundSrc') or '')}:"
                    f"{((completion_title_restore_state.get('battleCompletionFeedbackLast') or {}).get('battleCompletionSoundPlayed'))} "
                    f"noticeLines={'|'.join(str(line) for line in ((completion_title_restore_state.get('completionNotice') or {}).get('lines') or []))} "
                    f"progressReview={'|'.join(str(line) for line in ((completion_title_restore_state.get('progressReviewBlock') or {}).get('lines') or []))} "
                    f"battleVictoryCount={(completion_title_restore_state.get('progress') or {}).get('counts', {}).get('battle-victory')} "
                    f"{story_flag_report((completion_title_restore_state.get('progress') or {}).get('storyFlags'), 'titleBattleVictory')} "
                    f"drop={next((item.get('name') for item in ((completion_title_restore_state.get('runtimeState') or {}).get('items') or []) if item.get('key') == 'herb'), '')} "
                    f"dropCount={next((item.get('count') for item in ((completion_title_restore_state.get('runtimeState') or {}).get('items') or []) if item.get('key') == 'herb'), '')} "
                    f"expTotal={(completion_title_restore_state.get('runtimeState') or {}).get('expTotal')} "
                    f"levelAfter={(((completion_title_restore_state.get('runtimeState') or {}).get('characterExp') or [{}])[0]).get('level')} "
                    f"nextExp={(((completion_title_restore_state.get('runtimeState') or {}).get('characterExp') or [{}])[0]).get('exp')}/"
                    f"{(((completion_title_restore_state.get('runtimeState') or {}).get('characterExp') or [{}])[0]).get('expMax')} "
                    f"quickLoadText={completion_title_restore_state.get('quickLoadText')} "
                    "progressRestored=True "
                    "originalStoryFlagRuntimeImplemented=False"
                ),
                "runBattle": (
                    f"{(run_state.get('progressRecord') or {}).get('id')} "
                    f"notice={run_state.get('notice')} "
                    f"battleRunCount={(run_state.get('progress') or {}).get('counts', {}).get('battle-run')} "
                    f"battleVictoryCount={(run_state.get('progress') or {}).get('counts', {}).get('battle-victory', 0)} "
                    f"autoSource={(run_state.get('autoSave') or {}).get('source')} "
                    f"runTextTable={((run_state.get('runCommand') or {}).get('textTableKey') or '')} "
                    f"runTextRef={((run_state.get('runCommand') or {}).get('textTableRefVaHex') or '')} "
                    f"runTextVa={((run_state.get('runCommand') or {}).get('textTableTextVaHex') or '')} "
                    f"rewardGranted={((run_state.get('summary') or {}).get('rewardGranted') is True)} "
                    f"completionCompleted={((run_state.get('completion') or {}).get('battle') or {}).get('completed')} "
                    f"outcomeCompleted={((run_state.get('completion') or {}).get('battleOutcome') or {}).get('completed')} "
                    f"outcomeFeedback={((run_state.get('outcomeFeedbackLast') or {}).get('source') or '')}:"
                    f"{((run_state.get('outcomeFeedbackLast') or {}).get('text') or '')} "
                    f"outcomeFeedbackRender={bool(run_state.get('outcomeFeedbackRender') or [])} "
                    f"outcomeFeedbackDuration={((run_state.get('outcomeFeedbackLast') or {}).get('durationMs'))} "
                    f"outcomeSound={((run_state.get('outcomeFeedbackLast') or {}).get('battleOutcomeSound') or '')} "
                    f"outcomeSoundSrc={((run_state.get('outcomeFeedbackLast') or {}).get('battleOutcomeSoundSrc') or '')} "
                    f"outcomeSoundPlayed={((run_state.get('outcomeFeedbackLast') or {}).get('battleOutcomeSoundPlayed'))} "
                    f"browserOutcomeFeedback={((run_state.get('outcomeFeedbackLast') or {}).get('browserBattleOutcomeFeedbackImplemented'))} "
                    f"objective={((run_state.get('objectiveBefore') or {}).get('title') or '')} "
                    f"objectiveAction={((run_state.get('objectiveAction') or {}).get('action') or '')} "
                    f"objectiveActiveId={((run_state.get('objectiveAction') or {}).get('activeId') or '')} "
                    f"outcomeNoticeFeedback={((run_state.get('outcomeNoticeFeedbackLast') or {}).get('source') or '')}:"
                    f"{((run_state.get('outcomeNoticeFeedbackLast') or {}).get('text') or '')} "
                    f"outcomeNoticeFeedbackRender={bool(run_state.get('outcomeNoticeFeedbackRender') or [])} "
                    f"outcomeNoticeSound={((run_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSound') or '')}:"
                    f"{((run_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSoundSrc') or '')}:"
                    f"{((run_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSoundPlayed'))} "
                    f"runtimeMoney={(run_state.get('runtimeState') or {}).get('money')} "
                    f"runtimeExp={(run_state.get('runtimeState') or {}).get('expTotal')} "
                    "originalStoryFlagRuntimeImplemented=False"
                ),
                "runTitleContinue": (
                    "titleContinue=True "
                    f"label={next((label for label in (run_title_state.get('titleMenuLabels') or []) if '이어하기 map1_02b 11,12' in str(label)), '')} "
                    f"map={run_title_restore_state.get('map')} "
                    f"battleRunCount={(run_title_restore_state.get('progress') or {}).get('counts', {}).get('battle-run')} "
                    f"battleVictoryCount={(run_title_restore_state.get('progress') or {}).get('counts', {}).get('battle-victory', 0)} "
                    f"button={run_title_restore_state.get('buttonText')} "
                    f"completionCompleted={((run_title_restore_state.get('completion') or {}).get('battle') or {}).get('completed')} "
                    f"outcomeCompleted={((run_title_restore_state.get('completion') or {}).get('battleOutcome') or {}).get('completed')} "
                    f"objective={((run_title_restore_state.get('objectiveBefore') or {}).get('title') or '')} "
                    f"objectiveAction={((run_title_restore_state.get('objectiveAction') or {}).get('action') or '')} "
                    f"objectiveActiveId={((run_title_restore_state.get('objectiveAction') or {}).get('activeId') or '')} "
                    f"outcomeNoticeFeedback={((run_title_restore_state.get('outcomeNoticeFeedbackLast') or {}).get('source') or '')}:"
                    f"{((run_title_restore_state.get('outcomeNoticeFeedbackLast') or {}).get('text') or '')} "
                    f"outcomeNoticeFeedbackRender={bool(run_title_restore_state.get('outcomeNoticeFeedbackRender') or [])} "
                    f"outcomeNoticeSound={((run_title_restore_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSound') or '')}:"
                    f"{((run_title_restore_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSoundSrc') or '')}:"
                    f"{((run_title_restore_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSoundPlayed'))} "
                    f"runtimeMoney={(run_title_restore_state.get('runtimeState') or {}).get('money')} "
                    f"runtimeExp={(run_title_restore_state.get('runtimeState') or {}).get('expTotal')} "
                    f"quickLoadText={run_title_restore_state.get('quickLoadText')} "
                    "loadedSaveSummary=False"
                ),
                "defeatBattle": (
                    f"{(defeat_state.get('progressRecord') or {}).get('id')} "
                    f"notice={defeat_state.get('notice')} "
                    f"battleDefeatCount={(defeat_state.get('progress') or {}).get('counts', {}).get('battle-defeat')} "
                    f"battleVictoryCount={(defeat_state.get('progress') or {}).get('counts', {}).get('battle-victory', 0)} "
                    f"autoSource={(defeat_state.get('autoSave') or {}).get('source')} "
                    f"defendTextTable={defeat_defend_command.get('textTableKey') or ''} "
                    f"defendTextRef={defeat_defend_command.get('textTableRefVaHex') or ''} "
                    f"defendTextVa={defeat_defend_command.get('textTableTextVaHex') or ''} "
                    f"defendFeedback={defeat_defend_feedback.get('source') or ''}:{defeat_defend_feedback.get('text') or ''} "
                    f"defendFeedbackRender={bool(defeat_before_close.get('defendFeedbackRender') or [])} "
                    f"defendFeedbackDuration={defeat_defend_feedback.get('durationMs')} "
                    f"defendSound={defeat_defend_feedback.get('battleDefendSound') or ''} "
                    f"defendSoundSrc={defeat_defend_feedback.get('battleDefendSoundSrc') or ''} "
                    f"defendSoundPlayed={defeat_defend_feedback.get('battleDefendSoundPlayed')} "
                    f"browserDefendFeedback={defeat_defend_feedback.get('browserBattleDefendFeedbackImplemented')} "
                    f"rewardGranted={(defeat_before_close.get('rewardGranted')) is True} "
                    f"completionCompleted={((defeat_state.get('completion') or {}).get('battle') or {}).get('completed')} "
                    f"outcomeCompleted={((defeat_state.get('completion') or {}).get('battleOutcome') or {}).get('completed')} "
                    f"outcomeFeedback={((defeat_state.get('outcomeFeedbackLast') or {}).get('source') or '')}:"
                    f"{((defeat_state.get('outcomeFeedbackLast') or {}).get('text') or '')} "
                    f"outcomeFeedbackRender={bool(defeat_state.get('outcomeFeedbackRender') or [])} "
                    f"outcomeFeedbackDuration={((defeat_state.get('outcomeFeedbackLast') or {}).get('durationMs'))} "
                    f"outcomeSound={((defeat_state.get('outcomeFeedbackLast') or {}).get('battleOutcomeSound') or '')} "
                    f"outcomeSoundSrc={((defeat_state.get('outcomeFeedbackLast') or {}).get('battleOutcomeSoundSrc') or '')} "
                    f"outcomeSoundPlayed={((defeat_state.get('outcomeFeedbackLast') or {}).get('battleOutcomeSoundPlayed'))} "
                    f"browserOutcomeFeedback={((defeat_state.get('outcomeFeedbackLast') or {}).get('browserBattleOutcomeFeedbackImplemented'))} "
                    f"objective={((defeat_state.get('objectiveBefore') or {}).get('title') or '')} "
                    f"objectiveAction={((defeat_state.get('objectiveAction') or {}).get('action') or '')} "
                    f"objectiveActiveId={((defeat_state.get('objectiveAction') or {}).get('activeId') or '')} "
                    f"outcomeNoticeFeedback={((defeat_state.get('outcomeNoticeFeedbackLast') or {}).get('source') or '')}:"
                    f"{((defeat_state.get('outcomeNoticeFeedbackLast') or {}).get('text') or '')} "
                    f"outcomeNoticeFeedbackRender={bool(defeat_state.get('outcomeNoticeFeedbackRender') or [])} "
                    f"outcomeNoticeSound={((defeat_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSound') or '')}:"
                    f"{((defeat_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSoundSrc') or '')}:"
                    f"{((defeat_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSoundPlayed'))} "
                    f"battleClosed={not defeat_state.get('battleStateActive')} "
                    f"runtimeMoney={(((defeat_state.get('beforeClose') or {}).get('runtimeState') or {}).get('money'))} "
                    f"runtimeExp={(((defeat_state.get('beforeClose') or {}).get('runtimeState') or {}).get('expTotal'))} "
                    f"recovered={((defeat_state.get('defeatRecovery') or {}).get('recovered'))} "
                    f"target={((defeat_state.get('defeatRecovery') or {}).get('target'))} "
                    f"hp={((defeat_state.get('defeatRecovery') or {}).get('hpBefore'))}->{((defeat_state.get('defeatRecovery') or {}).get('hpAfter'))} "
                    "originalStoryFlagRuntimeImplemented=False"
                ),
                "defeatTitleContinue": (
                    "titleContinue=True "
                    f"label={next((label for label in (defeat_title_state.get('titleMenuLabels') or []) if '이어하기 map1_02b 11,12' in str(label)), '')} "
                    f"map={defeat_title_restore_state.get('map')} "
                    f"battleDefeatCount={(defeat_title_restore_state.get('progress') or {}).get('counts', {}).get('battle-defeat')} "
                    f"battleVictoryCount={(defeat_title_restore_state.get('progress') or {}).get('counts', {}).get('battle-victory', 0)} "
                    f"button={defeat_title_restore_state.get('buttonText')} "
                    f"completionCompleted={((defeat_title_restore_state.get('completion') or {}).get('battle') or {}).get('completed')} "
                    f"outcomeCompleted={((defeat_title_restore_state.get('completion') or {}).get('battleOutcome') or {}).get('completed')} "
                    f"objective={((defeat_title_restore_state.get('objectiveBefore') or {}).get('title') or '')} "
                    f"objectiveAction={((defeat_title_restore_state.get('objectiveAction') or {}).get('action') or '')} "
                    f"objectiveActiveId={((defeat_title_restore_state.get('objectiveAction') or {}).get('activeId') or '')} "
                    f"outcomeNoticeFeedback={((defeat_title_restore_state.get('outcomeNoticeFeedbackLast') or {}).get('source') or '')}:"
                    f"{((defeat_title_restore_state.get('outcomeNoticeFeedbackLast') or {}).get('text') or '')} "
                    f"outcomeNoticeFeedbackRender={bool(defeat_title_restore_state.get('outcomeNoticeFeedbackRender') or [])} "
                    f"outcomeNoticeSound={((defeat_title_restore_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSound') or '')}:"
                    f"{((defeat_title_restore_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSoundSrc') or '')}:"
                    f"{((defeat_title_restore_state.get('outcomeNoticeFeedbackLast') or {}).get('battleOutcomeSoundPlayed'))} "
                    f"runtimeMoney={(defeat_title_restore_state.get('runtimeState') or {}).get('money')} "
                    f"runtimeExp={(defeat_title_restore_state.get('runtimeState') or {}).get('expTotal')} "
                    f"recoveredHp={next((row.get('hp') for row in ((defeat_title_restore_state.get('runtimeState') or {}).get('partyHp') or []) if row.get('name') == 'Ataho'), '')} "
                    f"quickLoadText={defeat_title_restore_state.get('quickLoadText')} "
                    "loadedSaveSummary=False"
                ),
                "snapshots": {
                    "familyButton": family_button,
                    "familyBattle": family_state,
                    "pointerBattleCommand": pointer_attack_state,
                    "battleSkillAction": skill_action_state,
                    "itemTargetSelection": item_target_state,
                    "actionBattle": action_state,
                    "battleCandidateMenu": battle_menu_state,
                    "battleSpriteMenu": battle_sprite_menu_state,
                    "battleSpriteDirectUrl": battle_sprite_direct_state,
                    "dialogueBattleLink": dialogue_battle_state,
                    "fieldEncounter": field_encounter_state,
                    "fieldEncounterTitle": field_encounter_title_state,
                    "fieldEncounterTitleClick": field_encounter_title_click,
                    "fieldEncounterTitleRestore": field_encounter_title_restore_state,
                    "requestedButton": requested_button,
                    "requestedBattle": requested_state,
                    "paralysisTurn": paralysis_state,
                    "statusCure": status_cure_state,
                    "statusPersist": status_persist_state,
                    "statusPersistTitle": status_persist_title_state,
                    "statusPersistTitleClick": status_persist_title_click,
                    "statusPersistTitleRestore": status_persist_title_restore_state,
                    "statusEffectTurn": status_effect_state,
                    "completion": completion_state,
                    "completionTitle": completion_title_state,
                    "completionTitleClick": completion_title_click,
                    "completionTitleRestore": completion_title_restore_state,
                    "completionRestore": completion_restore_state,
                    "runBattle": run_state,
                    "runTitle": run_title_state,
                    "runTitleClick": run_title_click,
                    "runTitleRestore": run_title_restore_state,
                    "defeatBattle": defeat_state,
                    "defeatTitle": defeat_title_state,
                    "defeatTitleClick": defeat_title_click,
                    "defeatTitleRestore": defeat_title_restore_state,
                },
            }
            write_report(report)
            print(
                "ok candidate battle browser "
                f"family={report['mapFamilyBattle']} action={report['actionBattle']} "
                f"itemTarget={report['itemTargetSelection']} "
                f"menu={report['battleCandidateMenu']} "
                f"spriteMenu={report['battleSpriteMenu']} "
                f"spriteDirect={report['battleSpriteDirectUrl']} "
                f"dialogueBattleLink={report['dialogueBattleLink']} "
                f"fieldEncounter={report['fieldEncounter']} fieldEncounterTitle={report['fieldEncounterTitleContinue']} "
                f"requested={report['requestedBattle']} completed={report['completedBattle']} "
                f"titleContinue={report['completionTitleContinue']} "
                f"statusCure={report['statusCure']} statusPersist={report['statusPersist']} "
                f"statusPersistTitle={report['statusPersistTitleContinue']} "
                f"statusEffectTurn={report['statusEffectTurn']} "
                f"paralysisTurn={report['paralysisTurn']} run={report['runBattle']} "
                f"runTitle={report['runTitleContinue']} defeat={report['defeatBattle']} "
                f"defeatTitle={report['defeatTitleContinue']}"
            )
        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()
