#!/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,
    request_json,
    wait_for_driver,
    wait_for_map_runtime,
    wait_for_page,
)

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


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


def movement_trace_script() -> str:
    return """
const startPosition = playerPositionForTile(11, 12);
setPlayerPosition(startPosition.x, startPosition.y);
player.dir = 2;
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
lastMovementAction = null;
pendingMovementAction = null;
const startTile = footTile();
const movement = movementTargetFromTile(startTile, 1, 0);
if (!movement) {
  return { error: 'missing-right-movement', scene, map: map?.name || '', startTile };
}
recordPartyTrailPosition(movement.target);
player.step = {
  fromX: player.x,
  fromY: player.y,
  toX: movement.target.x,
  toY: movement.target.y,
  elapsed: 0,
  duration: TILE_STEP_SECONDS,
  walkScriptStartPhase: player.walkScriptPhase,
};
const samples = [];
function sample(label) {
  render();
  const partyRender = window.HWANSE_LAST_PARTY_RENDER || {};
  const playerRender = (partyRender.actors || []).find((actor) => actor.sprite === 'player') || null;
  const selector = originalPartyFrameSelector(player.dir, player.frame, player.moving, player.walkScriptPhase);
  const source = playerFrameSource();
  const expectedSource = PLAYER_ORIGINAL_SOURCE_RECTS[selector] || PLAYER_ORIGINAL_SOURCE_RECTS[0];
  const sourceRect = { x: source.x, y: source.y, width: PLAYER_FRAME_WIDTH, height: PLAYER_FRAME_HEIGHT };
  const expectedSourceRect = {
    x: expectedSource.x,
    y: expectedSource.y,
    width: PLAYER_FRAME_WIDTH,
    height: PLAYER_FRAME_HEIGHT,
  };
    samples.push({
    label,
    x: player.x,
    y: player.y,
    foot: footTile(),
    footprintTiles: standFootprintTiles(player.x, player.y).map(([x, y]) => ({ x, y })),
    frame: player.frame,
    selector,
    selectorHex: `0x${selector.toString(16).padStart(8, '0')}`,
    originalSelector: selector,
    sourceRect,
    expectedSourceRect,
    sourceMatchesSelector: (
      sourceRect.x === expectedSourceRect.x
      && sourceRect.y === expectedSourceRect.y
      && sourceRect.width === expectedSourceRect.width
      && sourceRect.height === expectedSourceRect.height
    ),
    moving: player.moving,
    walkScriptPhase: player.walkScriptPhase,
    stepElapsed: player.step ? player.step.elapsed : null,
    stepActive: !!player.step,
    camera: { x: camera.x, y: camera.y },
    renderScreen: playerRender ? { x: playerRender.screenX, y: playerRender.screenY } : null,
    projection: playerRender?.projection || null,
    projectionMatchesDraw: playerRender?.projection?.webProjectionMatchesDraw === true,
  });
}
sample('start');
for (let index = 1; index <= 4; index += 1) {
  update(ORIGINAL_FRAME_SECONDS);
  sample(`tick${index}`);
}
update(0);
sample('idle-after-complete');
return {
  scene,
  map: map?.name || '',
  originalFrameMs: ORIGINAL_FRAME_MS,
  originalFrameSeconds: ORIGINAL_FRAME_SECONDS,
  defaultTileStepMs: DEFAULT_TILE_STEP_MS,
  tileStepSeconds: TILE_STEP_SECONDS,
  playerOriginalTileStepCommands: PLAYER_ORIGINAL_TILE_STEP_COMMANDS,
  playerOriginalWalkCadenceCommands: PLAYER_ORIGINAL_WALK_CADENCE_COMMANDS,
  playerOriginalWalkScriptSelectors: PLAYER_ORIGINAL_WALK_SCRIPT_SELECTORS[player.dir],
  playerOriginalIdleSelector: PLAYER_ORIGINAL_IDLE_SELECTORS[player.dir],
  playerWalkFrames: PLAYER_WALK_FRAMES,
  playerFirstWalkFrame: PLAYER_FIRST_WALK_FRAME,
  playerFootprintRule: PLAYER_FOOTPRINT_RULE,
  playerFootTileSize: PLAYER_FOOT_TILE_SIZE,
  playerFootTileCenterOffsetX: PLAYER_FOOT_TILE_CENTER_OFFSET_X,
  playerFootTileBottomOffsetY: PLAYER_FOOT_TILE_BOTTOM_OFFSET_Y,
  playerTilePositionOffset: { x: PLAYER_TILE_POSITION_OFFSET_X, y: PLAYER_TILE_POSITION_OFFSET_Y },
  rightWalkSelectorSequence: PLAYER_ORIGINAL_WALK_SCRIPT_SELECTORS[2],
  rightIdleSelector: PLAYER_ORIGINAL_IDLE_SELECTORS[2],
  sourceRectWidth: PLAYER_FRAME_WIDTH,
  sourceRectHeight: PLAYER_FRAME_HEIGHT,
  originalProjectionBias: { x: ORIGINAL_DRAW_PROJECTION_BIAS_X, y: ORIGINAL_DRAW_PROJECTION_BIAS_Y },
  originalProjectionFixedShift: ORIGINAL_DRAW_PROJECTION_FIXED_SHIFT,
  originalProjectionFixedScale: ORIGINAL_DRAW_PROJECTION_FIXED_SCALE,
  originalProjectionTopOffset: {
    x: ORIGINAL_DRAW_PROJECTION_TOP_OFFSET_X,
    y: ORIGINAL_DRAW_PROJECTION_TOP_OFFSET_Y,
  },
  startTile,
  targetTile: movement.nextTile,
  from: { x: movement.target.x - map.tileSize, y: movement.target.y },
  target: movement.target,
  samples,
  search: location.search,
  partyTrailCursors,
  partyTrailSnapshot: partyTrail,
};
"""


def movement_step_sound_script() -> str:
    return """
activeDialogue = null;
menuOpen = false;
runtimeState = createPrototypeRuntimeState();
fieldEncounterState = createFieldEncounterState({ enabled: false });
{
  const position = playerPositionForTile(11, 12);
  setPlayerPosition(position.x, position.y);
}
updateCamera();
player.dir = 2;
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
lastMovementAction = null;
pendingMovementAction = null;
virtualMovementAction = null;
stepSoundAt = -1000;
window.HWANSE_SOUND_LOG = [];
window.HWANSE_SOUND_COUNTS = {};
window.HWANSE_LAST_SOUND = null;
window.HWANSE_FIELD_MOVEMENT_SOUND_LOG = [];
window.HWANSE_LAST_FIELD_MOVEMENT_SOUND = null;
sessionStorage.removeItem('HWANSE_LAST_FIELD_MOVEMENT_SOUND');
const beforeFoot = footTile();
const beforePosition = { x: player.x, y: player.y };
const downEvent = new KeyboardEvent('keydown', {
  code: 'ArrowRight',
  key: 'ArrowRight',
  bubbles: true,
  cancelable: true,
});
window.dispatchEvent(downEvent);
update(0);
const stepAfterInput = player.step ? {
  fromX: player.step.fromX,
  fromY: player.step.fromY,
  toX: player.step.toX,
  toY: player.step.toY,
  elapsed: player.step.elapsed,
  duration: player.step.duration,
} : null;
const soundAfterInput = window.HWANSE_LAST_FIELD_MOVEMENT_SOUND || null;
const upEvent = new KeyboardEvent('keyup', {
  code: 'ArrowRight',
  key: 'ArrowRight',
  bubbles: true,
  cancelable: true,
});
window.dispatchEvent(upEvent);
for (let index = 0; index < 4; index += 1) update(ORIGINAL_FRAME_SECONDS);
update(0);
if (typeof render === 'function') render();
return {
  scene,
  map: map?.name || '',
  beforeFoot,
  afterFoot: footTile(),
  beforePosition,
  afterPosition: { x: player.x, y: player.y },
  defaultPrevented: downEvent.defaultPrevented,
  keyReleased: !keyboardState.has(KEYBOARD_CODE_OFFSETS.ArrowRight),
  stepAfterInput,
  stepActiveAfterInput: Boolean(stepAfterInput),
  stepActiveAfterComplete: Boolean(player.step),
  fieldMovementSound: soundAfterInput,
  fieldMovementSoundLast: window.HWANSE_LAST_FIELD_MOVEMENT_SOUND || null,
  fieldMovementSoundLog: window.HWANSE_FIELD_MOVEMENT_SOUND_LOG || [],
  fieldMovementSoundLogLength: (window.HWANSE_FIELD_MOVEMENT_SOUND_LOG || []).length,
  soundCounts: { ...(window.HWANSE_SOUND_COUNTS || {}) },
  lastSound: window.HWANSE_LAST_SOUND || null,
  fieldMovementSoundLogLimit: FIELD_MOVEMENT_SOUND_LOG_LIMIT,
  originalStepSoundTimingImplemented: false,
  originalActorMovementRuntimeImplemented: false,
  originalStoryFlagRuntimeImplemented: false,
};
"""


def blocked_movement_bump_script() -> str:
    return """
window.HWANSE_MOVEMENT_BUMP_LOG = [];
window.HWANSE_MOVEMENT_BUMP_RENDER = [];
window.HWANSE_LAST_MOVEMENT_BUMP_EFFECT = null;
window.HWANSE_LAST_MOVEMENT_BUMP_RENDER = null;
window.HWANSE_MOVEMENT_BUMP_SOUND_LOG = [];
window.HWANSE_LAST_MOVEMENT_BUMP_SOUND = null;
window.HWANSE_SOUND_LOG = [];
window.HWANSE_SOUND_COUNTS = {};
activeMovementBumpEffect = null;
activeDialogue = null;
menuOpen = false;
runtimeState = createPrototypeRuntimeState();
fieldEncounterState = createFieldEncounterState({ enabled: false });
lastMovementAction = null;
pendingMovementAction = null;
virtualMovementAction = null;

const directions = [
  { name: 'left', action: 0, dx: -1, dy: 0 },
  { name: 'right', action: 1, dx: 1, dy: 0 },
  { name: 'up', action: 2, dx: 0, dy: -1 },
  { name: 'down', action: 3, dx: 0, dy: 1 },
];

function targetTileForAttempt(tile, direction) {
  return {
    x: clamp(tile.x + direction.dx, 0, map.width - 1),
    y: clamp(tile.y + direction.dy, 0, map.height - 1),
  };
}

function findBlockedMovementAttempt() {
  const originalWallSlide = { x: wallSlidePreference.x, y: wallSlidePreference.y };
  try {
    for (let y = 0; y < map.height; y += 1) {
      for (let x = 0; x < map.width; x += 1) {
        const tile = { x, y };
        const position = playerPositionForTile(x, y);
        if (!canMoveToTarget(position.x, position.y, 0, 0)) continue;
        for (const direction of directions) {
          const targetTile = targetTileForAttempt(tile, direction);
          if (targetTile.x === tile.x && targetTile.y === tile.y) continue;
          wallSlidePreference.x = 1;
          wallSlidePreference.y = 1;
          const primary = movementTargetFromTile(tile, direction.dx, direction.dy);
          wallSlidePreference.x = 1;
          wallSlidePreference.y = 1;
          const chosen = chooseMovementStep(tile, direction.dx, direction.dy);
          if (!primary && !chosen) {
            return { tile, targetTile, direction };
          }
        }
      }
    }
  } finally {
    wallSlidePreference.x = originalWallSlide.x;
    wallSlidePreference.y = originalWallSlide.y;
  }
  return null;
}

const attempt = findBlockedMovementAttempt();
if (!attempt) {
  return { error: 'no-blocked-movement-attempt', scene, map: map?.name || '' };
}
{
  const position = playerPositionForTile(attempt.tile.x, attempt.tile.y);
  setPlayerPosition(position.x, position.y);
}
updateCamera();
player.dir = attempt.direction.action === 0 ? 1 : attempt.direction.action === 1 ? 2 : attempt.direction.action === 2 ? 3 : 0;
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
const beforeFoot = footTile();
virtualMovementAction = attempt.direction.action;
update(0);
render();
const afterFoot = footTile();
virtualMovementAction = null;
pendingMovementAction = null;
lastMovementAction = null;
return {
  ok: true,
  scene,
  map: map?.name || '',
  attempt,
  beforeFoot,
  afterFoot,
  playerStepActive: !!player.step,
  playerFrame: player.frame,
  activeMovementBumpEffect: !!activeMovementBumpEffect,
  effect: window.HWANSE_LAST_MOVEMENT_BUMP_EFFECT || null,
  render: window.HWANSE_LAST_MOVEMENT_BUMP_RENDER || null,
  renderList: window.HWANSE_MOVEMENT_BUMP_RENDER || [],
  bumpSound: window.HWANSE_LAST_MOVEMENT_BUMP_SOUND || null,
  bumpSoundLog: window.HWANSE_MOVEMENT_BUMP_SOUND_LOG || [],
  bumpSoundLogLength: (window.HWANSE_MOVEMENT_BUMP_SOUND_LOG || []).length,
  movementBumpSoundLogLimit: MOVEMENT_BUMP_SOUND_LOG_LIMIT,
  soundCounts: window.HWANSE_SOUND_COUNTS || {},
  lastSound: (window.HWANSE_SOUND_LOG || []).slice(-1)[0] || null,
  logLength: (window.HWANSE_MOVEMENT_BUMP_LOG || []).length,
  movementBumpDurationMs: MOVEMENT_BUMP_EFFECT_DURATION_MS,
  movementBumpLogLimit: MOVEMENT_BUMP_EFFECT_LOG_LIMIT,
  originalActorInterpolationImplemented: false,
  originalObjectScriptCollisionRuntimeImplemented: false,
  originalStoryFlagRuntimeImplemented: false,
};
"""


def wall_slide_direction_script() -> str:
    return """
activeDialogue = null;
menuOpen = false;
runtimeState = createPrototypeRuntimeState();
fieldEncounterState = createFieldEncounterState({ enabled: false });
lastMovementAction = null;
pendingMovementAction = null;
virtualMovementAction = null;
stepSoundAt = -1000;
window.HWANSE_SOUND_LOG = [];
window.HWANSE_SOUND_COUNTS = {};
window.HWANSE_LAST_SOUND = null;
window.HWANSE_FIELD_MOVEMENT_SOUND_LOG = [];
window.HWANSE_LAST_FIELD_MOVEMENT_SOUND = null;
sessionStorage.removeItem('HWANSE_LAST_FIELD_MOVEMENT_SOUND');

const directions = [
  { name: 'left', action: 0, dx: -1, dy: 0 },
  { name: 'right', action: 1, dx: 1, dy: 0 },
  { name: 'up', action: 2, dx: 0, dy: -1 },
  { name: 'down', action: 3, dx: 0, dy: 1 },
];

function inputDirForAction(action) {
  if (action === 0) return 1;
  if (action === 1) return 2;
  if (action === 2) return 3;
  if (action === 3) return 0;
  return player.dir;
}

function findWallSlideAttempt() {
  const originalWallSlide = { x: wallSlidePreference.x, y: wallSlidePreference.y };
  try {
    for (let y = 0; y < map.height; y += 1) {
      for (let x = 0; x < map.width; x += 1) {
        const tile = { x, y };
        const position = playerPositionForTile(x, y);
        if (!canMoveToTarget(position.x, position.y, 0, 0)) continue;
        for (const direction of directions) {
          const directTarget = {
            x: clamp(tile.x + direction.dx, 0, map.width - 1),
            y: clamp(tile.y + direction.dy, 0, map.height - 1),
          };
          if (directTarget.x === tile.x && directTarget.y === tile.y) continue;
          wallSlidePreference.x = 1;
          wallSlidePreference.y = 1;
          const primary = movementTargetFromTile(tile, direction.dx, direction.dy);
          wallSlidePreference.x = 1;
          wallSlidePreference.y = 1;
          const movement = chooseMovementStep(tile, direction.dx, direction.dy);
          if (!primary && movement && (movement.dx !== direction.dx || movement.dy !== direction.dy)) {
            return { tile, direction, movement };
          }
        }
      }
    }
  } finally {
    wallSlidePreference.x = originalWallSlide.x;
    wallSlidePreference.y = originalWallSlide.y;
  }
  return null;
}

const attempt = findWallSlideAttempt();
if (!attempt) {
  return { error: 'no-wall-slide-attempt', scene, map: map?.name || '' };
}
const start = playerPositionForTile(attempt.tile.x, attempt.tile.y);
setPlayerPosition(start.x, start.y);
updateCamera();
player.dir = inputDirForAction(attempt.direction.action);
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
const inputDir = player.dir;
const expectedDir = directionForVector(attempt.movement.dx, attempt.movement.dy, inputDir);
const expectedAction = movementActionForVector(attempt.movement.dx, attempt.movement.dy);
const originalMapExit = checkMapExitTrialTransition;
const originalConfirmedTransition = checkConfirmedTransitionAttempt;
let updateError = null;
try {
  checkMapExitTrialTransition = () => false;
  checkConfirmedTransitionAttempt = () => false;
  wallSlidePreference.x = 1;
  wallSlidePreference.y = 1;
  virtualMovementAction = attempt.direction.action;
  update(0);
} catch (error) {
  updateError = String(error && error.message || error);
} finally {
  checkMapExitTrialTransition = originalMapExit;
  checkConfirmedTransitionAttempt = originalConfirmedTransition;
  virtualMovementAction = null;
  pendingMovementAction = null;
  lastMovementAction = null;
}
const stepAfterInput = player.step ? {
  fromX: player.step.fromX,
  fromY: player.step.fromY,
  toX: player.step.toX,
  toY: player.step.toY,
  elapsed: player.step.elapsed,
  duration: player.step.duration,
} : null;
const playerDirAfterInput = player.dir;
const soundAfterInput = window.HWANSE_LAST_FIELD_MOVEMENT_SOUND || null;
const trailEntry = partyTrail[partyTrailCursors[0]] || null;
for (let index = 0; index < 4; index += 1) update(ORIGINAL_FRAME_SECONDS);
update(0);
if (typeof render === 'function') render();
return {
  ok: !updateError,
  error: updateError,
  scene,
  map: map?.name || '',
  attempt: {
    tile: attempt.tile,
    input: attempt.direction,
    movement: attempt.movement,
  },
  inputDir,
  expectedDir,
  playerDirAfterInput,
  actualPlayerDir: player.dir,
  expectedAction,
  stepAfterInput,
  stepActiveAfterInput: Boolean(stepAfterInput),
  stepActiveAfterComplete: Boolean(player.step),
  afterFoot: footTile(),
  trailEntry,
  fieldMovementSound: soundAfterInput,
  fieldMovementSoundLog: window.HWANSE_FIELD_MOVEMENT_SOUND_LOG || [],
  soundCounts: { ...(window.HWANSE_SOUND_COUNTS || {}) },
  lastSound: window.HWANSE_LAST_SOUND || null,
  wallSlideActualDirectionImplemented: true,
  originalWallSlideFacingProofImplemented: false,
  originalStoryFlagRuntimeImplemented: false,
};
"""


def party_trail_animation_script() -> str:
    return """
activeDialogue = null;
menuOpen = false;
runtimeState = createPrototypeRuntimeState();
fieldEncounterState = createFieldEncounterState({ enabled: false });
manualPartyModeIndex = partyModeIndexForNames(['rinshan', 'smash']);
manualPartyMembers = partyMembersForMode(manualPartyModeIndex);
resetPartyTrail();
{
  const position = playerPositionForTile(11, 12);
  setPlayerPosition(position.x, position.y);
}
manualPartyModeIndex = partyModeIndexForNames(['rinshan', 'smash']);
manualPartyMembers = partyMembersForMode(manualPartyModeIndex);
resetPartyTrail();
player.dir = 2;
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
lastMovementAction = null;
pendingMovementAction = null;
virtualMovementAction = null;
wallSlideFollowupAction = null;

function actorSnapshot() {
  render();
  const renderState = window.HWANSE_LAST_PARTY_RENDER || {};
  return {
    player: (renderState.actors || []).find((actor) => actor.sprite === 'player') || null,
    followers: (renderState.actors || []).filter((actor) => actor.sprite !== 'player'),
    followerSteps: renderState.followerSteps || [],
    trail: renderState.trail || [],
    cursors: renderState.trailCursors || [],
  };
}

function completeCurrentStep() {
  let guard = 0;
  while (player.step && guard < 20) {
    update(ORIGINAL_FRAME_SECONDS / 2);
    guard += 1;
  }
  update(0);
}

function canFollowRouteFrom(tile, route) {
  let current = { ...tile };
  for (const [dx, dy] of route) {
    const nextTile = {
      x: clamp(current.x + dx, 0, map.width - 1),
      y: clamp(current.y + dy, 0, map.height - 1),
    };
    if (nextTile.x === current.x && nextTile.y === current.y) return false;
    const target = playerPositionForTile(nextTile.x, nextTile.y);
    if (!canMoveToTarget(target.x, target.y, dx, dy, current, nextTile)) return false;
    current = nextTile;
  }
  return true;
}

function findRouteStart(route) {
  for (let y = 1; y < map.height - 1; y += 1) {
    for (let x = 1; x < map.width - 1; x += 1) {
      const target = playerPositionForTile(x, y);
      if (canStandAt(target.x, target.y) && canFollowRouteFrom({ x, y }, route)) return { x, y };
    }
  }
  return null;
}

function startManualStep(dx, dy) {
  const tile = footTile();
  const movement = movementTargetFromTile(tile, dx, dy);
  if (!movement) return { error: 'blocked', tile, dx, dy };
  const dir = directionForVector(movement.dx, movement.dy, player.dir);
  player.dir = dir;
  recordPartyTrailPosition(movement.target, {
    fromX: player.x,
    fromY: player.y,
    dir,
  });
  player.step = {
    fromX: player.x,
    fromY: player.y,
    toX: movement.target.x,
    toY: movement.target.y,
    elapsed: 0,
    duration: TILE_STEP_SECONDS,
    walkScriptStartPhase: player.walkScriptPhase,
  };
  player.moving = true;
  player.frame = PLAYER_FIRST_WALK_FRAME;
  update(ORIGINAL_FRAME_SECONDS / 2);
  const mid = actorSnapshot();
  completeCurrentStep();
  const after = actorSnapshot();
  return {
    tile,
    dx,
    dy,
    movement,
    dir,
    mid,
    after,
  };
}

const overlapTargetTile = { x: 12, y: 12 };
const overlapTarget = playerPositionForTile(overlapTargetTile.x, overlapTargetTile.y);
partyTrail[partyTrailCursors[1]] = {
  x: overlapTarget.x,
  y: overlapTarget.y,
  fromX: overlapTarget.x,
  fromY: overlapTarget.y,
  dir: 2,
};
const overlapMoveAllowed = canMoveToTarget(overlapTarget.x, overlapTarget.y, 1, 0, footTile(), overlapTargetTile);
resetPartyTrail();
manualPartyModeIndex = partyModeIndexForNames(['rinshan', 'smash']);
manualPartyMembers = partyMembersForMode(manualPartyModeIndex);

const route = [
  [1, 0],
  [1, 0],
  [1, 0],
  [1, 0],
  [1, 0],
  [-1, 0],
  [-1, 0],
  [-1, 0],
];
const routeStart = findRouteStart(route);
if (!routeStart) {
  return {
    scene,
    map: map?.name || '',
    error: 'no-route-start',
    route,
  };
}
{
  const position = playerPositionForTile(routeStart.x, routeStart.y);
  setPlayerPosition(position.x, position.y);
}
manualPartyModeIndex = partyModeIndexForNames(['rinshan', 'smash']);
manualPartyMembers = partyMembersForMode(manualPartyModeIndex);
resetPartyTrail();
player.dir = 2;
const samples = route.map(([dx, dy]) => startManualStep(dx, dy));
const followerMovingSamples = samples.filter((sample) =>
  !(sample.error) && (sample.mid.followers || []).some((actor) => actor.moving === true),
);
const laggingTurnSample = samples.find((sample) =>
  !(sample.error) &&
  (sample.mid.followers || []).some((actor) => actor.moving === true && actor.dir !== sample.dir),
) || null;

return {
  scene,
  map: map?.name || '',
  partyMembers: activePartyMembers().map((member) => member.name),
  actorCollisionEnabled,
  overlapMoveAllowed,
  route,
  routeStart,
  samples,
  followerMovingSampleCount: followerMovingSamples.length,
  laggingTurnSample,
  partyFollowerSegmentInterpolationImplemented: true,
  partyActorsPassThroughByDefault: actorCollisionEnabled === false && overlapMoveAllowed === true,
  originalStoryFlagRuntimeImplemented: false,
};
"""


def field_poison_trace_script() -> str:
    return """
localStorage.removeItem(RUNTIME_SAVE_KEY);
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
runtimeState = createPrototypeRuntimeState();
const ataho = (runtimeState.characters || []).find((member) => member.name === 'Ataho');
ataho.hp = 36;
ataho.hpMax = 36;
ataho.statuses = ['poison'];
ataho.fieldPoisonStepCount = 0;
{
  const position = playerPositionForTile(11, 12);
  setPlayerPosition(position.x, position.y);
}
player.dir = 2;
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
fieldEncounterState = createFieldEncounterState({ enabled: false });
lastMovementAction = null;
pendingMovementAction = null;
window.HWANSE_LAST_FIELD_STATUS_EFFECT = null;
window.HWANSE_LAST_FIELD_STATUS_EFFECT_AUTO_SAVE = null;
window.HWANSE_LAST_FIELD_STATUS_STEP = null;
window.HWANSE_LAST_FIELD_STATUS_STEP_AUTO_SAVE = null;
window.HWANSE_FIELD_STATUS_FEEDBACK_LOG = [];
window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER = [];
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK = null;
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER = null;
activeFieldStatusFeedbacks = [];
const movementSteps = [];
function stepTile(dx, dy) {
  const beforeFoot = footTile();
  const beforeHp = ataho.hp;
  const beforeCounter = ataho.fieldPoisonStepCount || 0;
  const movement = movementTargetFromTile(beforeFoot, dx, dy);
  if (!movement) {
    movementSteps.push({ error: 'blocked', beforeFoot, dx, dy });
    return false;
  }
  recordPartyTrailPosition(movement.target);
  player.step = {
    fromX: player.x,
    fromY: player.y,
    toX: movement.target.x,
    toY: movement.target.y,
    elapsed: 0,
    duration: TILE_STEP_SECONDS,
    walkScriptStartPhase: player.walkScriptPhase,
  };
  for (let index = 0; index < 4; index += 1) update(ORIGINAL_FRAME_SECONDS);
  update(0);
  let savedAfterStep = null;
  try {
    savedAfterStep = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
  } catch (error) {
    savedAfterStep = { error: String(error && error.message || error) };
  }
  const savedAfterStepAtaho = (savedAfterStep?.runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
  movementSteps.push({
    beforeFoot,
    afterFoot: footTile(),
    beforeHp,
    afterHp: ataho.hp,
    beforeCounter,
    afterCounter: ataho.fieldPoisonStepCount || 0,
    fieldStatusStep: window.HWANSE_LAST_FIELD_STATUS_STEP || null,
    fieldStatusStepAutoSave: window.HWANSE_LAST_FIELD_STATUS_STEP_AUTO_SAVE || null,
    fieldStatus: window.HWANSE_LAST_FIELD_STATUS_EFFECT || null,
    savedAfterStepAtaho: savedAfterStepAtaho ? {
      hp: savedAfterStepAtaho.hp,
      statuses: [...(savedAfterStepAtaho.statuses || [])],
      fieldPoisonStepCount: savedAfterStepAtaho.fieldPoisonStepCount || 0,
    } : null,
  });
  return true;
}
const ok =
  stepTile(1, 0) &&
  stepTile(-1, 0) &&
  stepTile(1, 0) &&
  stepTile(-1, 0);
render();
const effectFeedbackLog = [...(window.HWANSE_FIELD_STATUS_FEEDBACK_LOG || [])];
const effectFeedbackRender = [...(window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER || [])];
const effectFeedbackLast = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK || null;
const effectFeedbackLastRender = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER || null;
let savedPayload = null;
try {
  savedPayload = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
} catch (error) {
  savedPayload = { error: String(error && error.message || error) };
}
const savedAtaho = (savedPayload?.runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
const objectiveBefore = typeof prototypeObjectiveState === 'function' ? prototypeObjectiveState() : null;
window.HWANSE_LAST_FIELD_STATUS_COMPLETION_NOTICE = null;
window.HWANSE_LAST_OBJECTIVE_ACTION = null;
const objectiveActionResult = typeof activatePrototypeObjectiveAction === 'function' ? activatePrototypeObjectiveAction() : false;
const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
const completionNotice = window.HWANSE_LAST_FIELD_STATUS_COMPLETION_NOTICE || null;
if (typeof render === 'function') render();
const noticeFeedbackLog = [...(window.HWANSE_FIELD_STATUS_FEEDBACK_LOG || [])];
const noticeFeedbackRender = [...(window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER || [])];
const noticeFeedbackLast = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK || null;
const noticeFeedbackLastRender = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER || null;
return {
  ok,
  scene,
  map: map?.name || '',
  startStatus: 'poison',
  fieldPoisonStepInterval: FIELD_POISON_STEP_INTERVAL,
  movementSteps,
  ataho: {
    name: ataho.name,
    hp: ataho.hp,
    hpMax: ataho.hpMax,
    statuses: [...(ataho.statuses || [])],
    fieldPoisonStepCount: ataho.fieldPoisonStepCount || 0,
  },
  fieldStatusEffect: window.HWANSE_LAST_FIELD_STATUS_EFFECT || null,
  fieldStatusAutoSave: window.HWANSE_LAST_FIELD_STATUS_EFFECT_AUTO_SAVE || null,
  fieldStatusFeedbackLog: effectFeedbackLog,
  fieldStatusFeedbackRender: effectFeedbackRender,
  fieldStatusFeedbackLast: effectFeedbackLast,
  fieldStatusFeedbackLastRender: effectFeedbackLastRender,
  fieldStatusNoticeFeedbackLog: noticeFeedbackLog,
  fieldStatusNoticeFeedbackRender: noticeFeedbackRender,
  fieldStatusNoticeFeedbackLast: noticeFeedbackLast,
  fieldStatusNoticeFeedbackLastRender: noticeFeedbackLastRender,
  objectiveBefore,
  objectiveActionResult,
  objectiveAction,
  completionNotice,
  playHudLines: typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []),
  progress: window.HWANSE_LAST_PROTOTYPE_PROGRESS || null,
  savedPayload,
  savedAtaho: savedAtaho ? {
    name: savedAtaho.name,
    hp: savedAtaho.hp,
    hpMax: savedAtaho.hpMax,
    statuses: [...(savedAtaho.statuses || [])],
    fieldPoisonStepCount: savedAtaho.fieldPoisonStepCount || 0,
  } : null,
  originalStatusFormulaImplemented: false,
  originalStoryFlagRuntimeImplemented: false,
};
"""


def field_poison_partial_continue_script() -> str:
    return """
window.__hwanseFieldPoisonPartialContinue = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
activeDialogue = null;
menuOpen = false;
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
runtimeState = createPrototypeRuntimeState();
const ataho = (runtimeState.characters || []).find((member) => member.name === 'Ataho');
ataho.hp = 36;
ataho.hpMax = 36;
ataho.statuses = ['poison'];
ataho.fieldPoisonStepCount = 0;
{
  const position = playerPositionForTile(11, 12);
  setPlayerPosition(position.x, position.y);
}
player.dir = 2;
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
fieldEncounterState = createFieldEncounterState({ enabled: false });
lastMovementAction = null;
pendingMovementAction = null;
virtualMovementAction = null;
window.HWANSE_LAST_FIELD_STATUS_STEP = null;
window.HWANSE_LAST_FIELD_STATUS_STEP_AUTO_SAVE = null;
window.HWANSE_FIELD_STATUS_FEEDBACK_LOG = [];
window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER = [];
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK = null;
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER = null;
activeFieldStatusFeedbacks = [];

function moveTileForPartialContinue(dx, dy) {
  const beforeFoot = footTile();
  const movement = movementTargetFromTile(beforeFoot, dx, dy);
  if (!movement) return false;
  recordPartyTrailPosition(movement.target);
  player.step = {
    fromX: player.x,
    fromY: player.y,
    toX: movement.target.x,
    toY: movement.target.y,
    elapsed: 0,
    duration: TILE_STEP_SECONDS,
    walkScriptStartPhase: player.walkScriptPhase,
  };
  for (let index = 0; index < 4; index += 1) update(ORIGINAL_FRAME_SECONDS);
  update(0);
  return true;
}

const moved =
  moveTileForPartialContinue(1, 0) &&
  moveTileForPartialContinue(-1, 0);
if (typeof render === 'function') render();
const fieldStatusFeedbackLog = window.HWANSE_FIELD_STATUS_FEEDBACK_LOG || [];
const fieldStatusFeedbackRender = window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER || [];
const fieldStatusFeedbackLast = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK || null;
const fieldStatusFeedbackLastRender = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER || null;
let savedBeforeTitle = null;
try {
  savedBeforeTitle = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
} catch (error) {
  savedBeforeTitle = { error: String(error && error.message || error) };
}
const savedBeforeTitleAtaho = (savedBeforeTitle?.runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
const stepAutoSave = window.HWANSE_LAST_FIELD_STATUS_STEP_AUTO_SAVE || null;
const stepResult = window.HWANSE_LAST_FIELD_STATUS_STEP || null;
const titleReturned = returnToTitleScreen();
const titleItemsBefore = titleMenuItems();
const continueIndex = titleItemsBefore.findIndex((item) => item.key === 'continue');
if (moved && titleReturned && continueIndex >= 0) {
  selectedTitleMenuIndex = continueIndex;
  activateSelectedTitleMenuItem();
}
const deadline = performance.now() + 3000;
function finishPartialContinueWhenReady() {
  const restoredAtaho = (runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
  const playHud = typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []);
  const statusMenuState = typeof partyStatusMenuState === 'function' ? partyStatusMenuState() : null;
  const statusMenuLines = typeof statusMenuReviewBlock === 'function' ? statusMenuReviewBlock(statusMenuState).lines : [];
  const ready = (
    moved &&
    titleReturned &&
    continueIndex >= 0 &&
    scene === 'map' &&
    map?.name === 'map1_02b' &&
    restoredAtaho &&
    restoredAtaho.hp === 36 &&
    (restoredAtaho.fieldPoisonStepCount || 0) === 2
  );
  if (!ready && performance.now() < deadline) {
    requestAnimationFrame(finishPartialContinueWhenReady);
    return;
  }
  window.__hwanseFieldPoisonPartialContinue = {
    ok: ready,
    moved,
    titleReturned,
    continueIndex,
    titleItemsBefore: titleItemsBefore.map((item) => ({ key: item.key, label: item.label })),
    scene,
    map: map?.name || '',
    foot: typeof footTile === 'function' ? footTile() : null,
    quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
    savedBeforeTitle,
    savedBeforeTitleAtaho: savedBeforeTitleAtaho ? {
      name: savedBeforeTitleAtaho.name,
      hp: savedBeforeTitleAtaho.hp,
      hpMax: savedBeforeTitleAtaho.hpMax,
      statuses: [...(savedBeforeTitleAtaho.statuses || [])],
      fieldPoisonStepCount: savedBeforeTitleAtaho.fieldPoisonStepCount || 0,
    } : null,
    stepResult,
    stepAutoSave,
    fieldStatusFeedbackLog,
    fieldStatusFeedbackRender,
    fieldStatusFeedbackLast,
    fieldStatusFeedbackLastRender,
    statusMenuState,
    statusMenuLines,
    restoredAtaho: restoredAtaho ? {
      name: restoredAtaho.name,
      hp: restoredAtaho.hp,
      hpMax: restoredAtaho.hpMax,
      statuses: [...(restoredAtaho.statuses || [])],
      fieldPoisonStepCount: restoredAtaho.fieldPoisonStepCount || 0,
    } : null,
    playHudLines: playHud,
    originalStatusFormulaImplemented: false,
    originalStoryFlagRuntimeImplemented: false,
  };
}
finishPartialContinueWhenReady();
return true;
"""


def field_poison_partial_continue_state_script() -> str:
    return """
return window.__hwanseFieldPoisonPartialContinue || {
  pending: true,
  scene: typeof scene === 'undefined' ? '' : scene,
  map: typeof map === 'undefined' || !map ? '' : map.name,
  titleItems: typeof titleMenuItems === 'function' ? titleMenuItems().map((item) => item.label) : [],
};
"""


def field_poison_cure_continue_script() -> str:
    return """
window.__hwanseFieldPoisonCureContinue = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
activeDialogue = null;
menuOpen = false;
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
runtimeState = createPrototypeRuntimeState();
const ataho = (runtimeState.characters || []).find((member) => member.name === 'Ataho');
ataho.hp = 36;
ataho.hpMax = 36;
ataho.statuses = ['poison'];
ataho.fieldPoisonStepCount = 2;
let antidote = runtimeState.items.find((item) => item.key === 'item_2');
if (!antidote) {
  antidote = { key: 'item_2', name: saveItemName('item_2', '해독초'), count: 0 };
  runtimeState.items.push(antidote);
}
antidote.name = saveItemName('item_2', '해독초');
antidote.count = 1;
{
  const position = playerPositionForTile(11, 12);
  setPlayerPosition(position.x, position.y);
}
player.dir = 2;
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
fieldEncounterState = createFieldEncounterState({ enabled: false });
lastMovementAction = null;
pendingMovementAction = null;
virtualMovementAction = null;
window.HWANSE_LAST_INVENTORY_ITEM_USE = null;
window.HWANSE_LAST_INVENTORY_ITEM_USE_AUTO_SAVE = null;
window.HWANSE_FIELD_STATUS_FEEDBACK_LOG = [];
window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER = [];
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK = null;
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER = null;
activeFieldStatusFeedbacks = [];
const staleFeedbackBeforeUse = recordFieldStatusFeedback({
  source: 'prototype-field-status-effect',
  effect: {
    key: 'poison',
    name: '독',
    characterName: 'Ataho',
    damage: 2,
    hpBefore: 36,
    hpAfter: 34,
    source: 'prototype-field-status-effect',
  },
});
const staleFeedbackCountBeforeUse = activeFieldStatusFeedbacks.length;
const used = useInventoryItem(antidote);
const itemNotice = menuNotice;
const staleFeedbackAfterUse = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK || null;
const staleFeedbackRenderAfterUse = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER || null;
const staleFeedbackCountAfterUse = activeFieldStatusFeedbacks.length;
let savedBeforeTitle = null;
try {
  savedBeforeTitle = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
} catch (error) {
  savedBeforeTitle = { error: String(error && error.message || error) };
}
const savedBeforeTitleAtaho = (savedBeforeTitle?.runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
const savedBeforeTitleAntidote = (savedBeforeTitle?.runtimeState?.items || []).find((item) => item.key === 'item_2') || null;
const itemUse = window.HWANSE_LAST_INVENTORY_ITEM_USE || null;
const itemAutoSave = window.HWANSE_LAST_INVENTORY_ITEM_USE_AUTO_SAVE || null;
const titleReturned = returnToTitleScreen();
const titleItemsBefore = titleMenuItems();
const continueIndex = titleItemsBefore.findIndex((item) => item.key === 'continue');
if (used && titleReturned && continueIndex >= 0) {
  selectedTitleMenuIndex = continueIndex;
  activateSelectedTitleMenuItem();
}
const deadline = performance.now() + 3000;
function finishCureContinueWhenReady() {
  const restoredAtaho = (runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
  const restoredAntidote = (runtimeState?.items || []).find((item) => item.key === 'item_2') || null;
  const playHud = typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []);
  const statusMenuState = typeof partyStatusMenuState === 'function' ? partyStatusMenuState() : null;
  const statusMenuLines = typeof statusMenuReviewBlock === 'function' ? statusMenuReviewBlock(statusMenuState).lines : [];
  const ready = (
    used &&
    titleReturned &&
    continueIndex >= 0 &&
    scene === 'map' &&
    map?.name === 'map1_02b' &&
    restoredAtaho &&
    restoredAtaho.hp === 36 &&
    !(restoredAtaho.statuses || []).includes('poison') &&
    (restoredAtaho.fieldPoisonStepCount || 0) === 0
  );
  if (!ready && performance.now() < deadline) {
    requestAnimationFrame(finishCureContinueWhenReady);
    return;
  }
  window.__hwanseFieldPoisonCureContinue = {
    ok: ready,
    used,
    titleReturned,
    continueIndex,
    titleItemsBefore: titleItemsBefore.map((item) => ({ key: item.key, label: item.label })),
    scene,
    map: map?.name || '',
    foot: typeof footTile === 'function' ? footTile() : null,
    quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
    menuNotice,
    itemNotice,
    itemUse,
    itemAutoSave,
    staleFeedbackBeforeUse,
    staleFeedbackAfterUse,
    staleFeedbackRenderAfterUse,
    staleFeedbackCountBeforeUse,
    staleFeedbackCountAfterUse,
    savedBeforeTitle,
    savedBeforeTitleAtaho: savedBeforeTitleAtaho ? {
      name: savedBeforeTitleAtaho.name,
      hp: savedBeforeTitleAtaho.hp,
      hpMax: savedBeforeTitleAtaho.hpMax,
      statuses: [...(savedBeforeTitleAtaho.statuses || [])],
      fieldPoisonStepCount: savedBeforeTitleAtaho.fieldPoisonStepCount || 0,
    } : null,
    savedBeforeTitleAntidote: savedBeforeTitleAntidote ? {
      key: savedBeforeTitleAntidote.key,
      name: savedBeforeTitleAntidote.name,
      count: savedBeforeTitleAntidote.count,
    } : null,
    restoredAtaho: restoredAtaho ? {
      name: restoredAtaho.name,
      hp: restoredAtaho.hp,
      hpMax: restoredAtaho.hpMax,
      statuses: [...(restoredAtaho.statuses || [])],
      fieldPoisonStepCount: restoredAtaho.fieldPoisonStepCount || 0,
    } : null,
    restoredAntidote: restoredAntidote ? {
      key: restoredAntidote.key,
      name: restoredAntidote.name,
      count: restoredAntidote.count,
    } : null,
    playHudLines: playHud,
    statusMenuState,
    statusMenuLines,
    originalStatusFormulaImplemented: false,
    originalStoryFlagRuntimeImplemented: false,
  };
}
finishCureContinueWhenReady();
return true;
"""


def field_poison_cure_continue_state_script() -> str:
    return """
return window.__hwanseFieldPoisonCureContinue || {
  pending: true,
  scene: typeof scene === 'undefined' ? '' : scene,
  map: typeof map === 'undefined' || !map ? '' : map.name,
  titleItems: typeof titleMenuItems === 'function' ? titleMenuItems().map((item) => item.label) : [],
};
"""


def field_paralysis_cure_continue_script() -> str:
    return """
window.__hwanseFieldParalysisCureContinue = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
activeDialogue = null;
menuOpen = false;
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
runtimeState = createPrototypeRuntimeState();
const ataho = (runtimeState.characters || []).find((member) => member.name === 'Ataho');
ataho.hp = 36;
ataho.hpMax = 36;
ataho.statuses = ['paralysis'];
ataho.fieldParalysisMoveCount = 2;
let antidote = runtimeState.items.find((item) => item.key === 'item_2');
if (!antidote) {
  antidote = { key: 'item_2', name: saveItemName('item_2', '해독초'), count: 0 };
  runtimeState.items.push(antidote);
}
antidote.name = saveItemName('item_2', '해독초');
antidote.count = 1;
{
  const position = playerPositionForTile(11, 12);
  setPlayerPosition(position.x, position.y);
}
player.dir = 2;
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
fieldEncounterState = createFieldEncounterState({ enabled: false });
lastMovementAction = null;
pendingMovementAction = null;
virtualMovementAction = null;
window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_BLOCK = {
  map: map?.name || '',
  blocks: [{ key: 'paralysis', name: '마비', characterName: 'Ataho' }],
};
window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_BLOCK_AUTO_SAVE = { source: 'synthetic-stale' };
window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_STEP = {
  map: map?.name || '',
  counters: [{ key: 'paralysis', name: '마비', characterName: 'Ataho', moveCount: 2, moveInterval: 3 }],
};
window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_STEP_AUTO_SAVE = { source: 'synthetic-stale' };
window.HWANSE_LAST_INVENTORY_ITEM_USE = null;
window.HWANSE_LAST_INVENTORY_ITEM_USE_AUTO_SAVE = null;
window.HWANSE_FIELD_STATUS_FEEDBACK_LOG = [];
window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER = [];
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK = null;
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER = null;
activeFieldStatusFeedbacks = [];
const staleFeedbackBeforeUse = recordFieldStatusFeedback({
  source: 'prototype-field-status-movement-block',
  blocked: true,
  block: {
    key: 'paralysis',
    name: '마비',
    characterName: 'Ataho',
    blocked: true,
    fromTile: footTile(),
    targetTile: { x: footTile().x + 1, y: footTile().y },
    source: 'prototype-field-status-movement-block',
  },
});
const staleFeedbackCountBeforeUse = activeFieldStatusFeedbacks.length;
const used = useInventoryItem(antidote);
const itemNotice = menuNotice;
const staleBlockAfterUse = window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_BLOCK || null;
const staleStepAfterUse = window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_STEP || null;
const staleFeedbackAfterUse = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK || null;
const staleFeedbackRenderAfterUse = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER || null;
const staleFeedbackCountAfterUse = activeFieldStatusFeedbacks.length;
let savedBeforeTitle = null;
try {
  savedBeforeTitle = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
} catch (error) {
  savedBeforeTitle = { error: String(error && error.message || error) };
}
const savedBeforeTitleAtaho = (savedBeforeTitle?.runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
const savedBeforeTitleAntidote = (savedBeforeTitle?.runtimeState?.items || []).find((item) => item.key === 'item_2') || null;
const itemUse = window.HWANSE_LAST_INVENTORY_ITEM_USE || null;
const itemAutoSave = window.HWANSE_LAST_INVENTORY_ITEM_USE_AUTO_SAVE || null;
const titleReturned = returnToTitleScreen();
const titleItemsBefore = titleMenuItems();
const continueIndex = titleItemsBefore.findIndex((item) => item.key === 'continue');
if (used && titleReturned && continueIndex >= 0) {
  selectedTitleMenuIndex = continueIndex;
  activateSelectedTitleMenuItem();
}
const deadline = performance.now() + 3000;
function finishParalysisCureContinueWhenReady() {
  const restoredAtaho = (runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
  const restoredAntidote = (runtimeState?.items || []).find((item) => item.key === 'item_2') || null;
  const playHud = typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []);
  const statusMenuState = typeof partyStatusMenuState === 'function' ? partyStatusMenuState() : null;
  const statusMenuLines = typeof statusMenuReviewBlock === 'function' ? statusMenuReviewBlock(statusMenuState).lines : [];
  const ready = (
    used &&
    titleReturned &&
    continueIndex >= 0 &&
    scene === 'map' &&
    map?.name === 'map1_02b' &&
    restoredAtaho &&
    restoredAtaho.hp === 36 &&
    !(restoredAtaho.statuses || []).includes('paralysis') &&
    (restoredAtaho.fieldParalysisMoveCount || 0) === 0
  );
  if (!ready && performance.now() < deadline) {
    requestAnimationFrame(finishParalysisCureContinueWhenReady);
    return;
  }
  window.__hwanseFieldParalysisCureContinue = {
    ok: ready,
    used,
    titleReturned,
    continueIndex,
    titleItemsBefore: titleItemsBefore.map((item) => ({ key: item.key, label: item.label })),
    scene,
    map: map?.name || '',
    foot: typeof footTile === 'function' ? footTile() : null,
    quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
    menuNotice,
    itemNotice,
    itemUse,
    itemAutoSave,
    staleBlockAfterUse,
    staleStepAfterUse,
    staleFeedbackBeforeUse,
    staleFeedbackAfterUse,
    staleFeedbackRenderAfterUse,
    staleFeedbackCountBeforeUse,
    staleFeedbackCountAfterUse,
    savedBeforeTitle,
    savedBeforeTitleAtaho: savedBeforeTitleAtaho ? {
      name: savedBeforeTitleAtaho.name,
      hp: savedBeforeTitleAtaho.hp,
      hpMax: savedBeforeTitleAtaho.hpMax,
      statuses: [...(savedBeforeTitleAtaho.statuses || [])],
      fieldParalysisMoveCount: savedBeforeTitleAtaho.fieldParalysisMoveCount || 0,
    } : null,
    savedBeforeTitleAntidote: savedBeforeTitleAntidote ? {
      key: savedBeforeTitleAntidote.key,
      name: savedBeforeTitleAntidote.name,
      count: savedBeforeTitleAntidote.count,
    } : null,
    restoredAtaho: restoredAtaho ? {
      name: restoredAtaho.name,
      hp: restoredAtaho.hp,
      hpMax: restoredAtaho.hpMax,
      statuses: [...(restoredAtaho.statuses || [])],
      fieldParalysisMoveCount: restoredAtaho.fieldParalysisMoveCount || 0,
    } : null,
    restoredAntidote: restoredAntidote ? {
      key: restoredAntidote.key,
      name: restoredAntidote.name,
      count: restoredAntidote.count,
    } : null,
    playHudLines: playHud,
    statusMenuState,
    statusMenuLines,
    originalStatusFormulaImplemented: false,
    originalStoryFlagRuntimeImplemented: false,
  };
}
finishParalysisCureContinueWhenReady();
return true;
"""


def field_paralysis_cure_continue_state_script() -> str:
    return """
return window.__hwanseFieldParalysisCureContinue || {
  pending: true,
  scene: typeof scene === 'undefined' ? '' : scene,
  map: typeof map === 'undefined' || !map ? '' : map.name,
  titleItems: typeof titleMenuItems === 'function' ? titleMenuItems().map((item) => item.label) : [],
};
"""


def field_poison_effect_continue_script() -> str:
    return """
window.__hwanseFieldPoisonEffectContinue = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
activeDialogue = null;
menuOpen = false;
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
runtimeState = createPrototypeRuntimeState();
const ataho = (runtimeState.characters || []).find((member) => member.name === 'Ataho');
ataho.hp = 36;
ataho.hpMax = 36;
ataho.statuses = ['poison'];
ataho.fieldPoisonStepCount = 0;
{
  const position = playerPositionForTile(11, 12);
  setPlayerPosition(position.x, position.y);
}
player.dir = 2;
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
fieldEncounterState = createFieldEncounterState({ enabled: false });
lastMovementAction = null;
pendingMovementAction = null;
virtualMovementAction = null;
window.HWANSE_LAST_FIELD_STATUS_EFFECT = null;
window.HWANSE_LAST_FIELD_STATUS_EFFECT_AUTO_SAVE = null;
window.HWANSE_LAST_FIELD_STATUS_STEP = null;
window.HWANSE_LAST_FIELD_STATUS_STEP_AUTO_SAVE = null;

function moveTileForEffectContinue(dx, dy) {
  const beforeFoot = footTile();
  const movement = movementTargetFromTile(beforeFoot, dx, dy);
  if (!movement) return false;
  recordPartyTrailPosition(movement.target);
  player.step = {
    fromX: player.x,
    fromY: player.y,
    toX: movement.target.x,
    toY: movement.target.y,
    elapsed: 0,
    duration: TILE_STEP_SECONDS,
    walkScriptStartPhase: player.walkScriptPhase,
  };
  for (let index = 0; index < 4; index += 1) update(ORIGINAL_FRAME_SECONDS);
  update(0);
  return true;
}

const moved =
  moveTileForEffectContinue(1, 0) &&
  moveTileForEffectContinue(-1, 0) &&
  moveTileForEffectContinue(1, 0) &&
  moveTileForEffectContinue(-1, 0);
const effectBeforeTitle = window.HWANSE_LAST_FIELD_STATUS_EFFECT || null;
const fieldStatusAutoSave = window.HWANSE_LAST_FIELD_STATUS_EFFECT_AUTO_SAVE || null;
let savedBeforeTitle = null;
try {
  savedBeforeTitle = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
} catch (error) {
  savedBeforeTitle = { error: String(error && error.message || error) };
}
const savedBeforeTitleAtaho = (savedBeforeTitle?.runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
const objectiveBeforeTitle = typeof prototypeObjectiveState === 'function' ? prototypeObjectiveState() : null;
const titleReturned = returnToTitleScreen();
const titleItemsBefore = titleMenuItems();
const continueIndex = titleItemsBefore.findIndex((item) => item.key === 'continue');
if (moved && titleReturned && continueIndex >= 0) {
  selectedTitleMenuIndex = continueIndex;
  activateSelectedTitleMenuItem();
}
const deadline = performance.now() + 3000;
function finishEffectContinueWhenReady() {
  const restoredAtaho = (runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
  const objectiveAfterContinue = typeof prototypeObjectiveState === 'function' ? prototypeObjectiveState() : null;
  const ready = (
    moved &&
    titleReturned &&
    continueIndex >= 0 &&
    scene === 'map' &&
    map?.name === 'map1_02b' &&
    restoredAtaho &&
    restoredAtaho.hp === 34 &&
    (restoredAtaho.statuses || []).includes('poison') &&
    (restoredAtaho.fieldPoisonStepCount || 0) === 0 &&
    objectiveAfterContinue?.title === '후보 필드 상태 완료 map1_02b'
  );
  if (!ready && performance.now() < deadline) {
    requestAnimationFrame(finishEffectContinueWhenReady);
    return;
  }
  window.HWANSE_LAST_FIELD_STATUS_COMPLETION_NOTICE = null;
  window.HWANSE_LAST_OBJECTIVE_ACTION = null;
  const objectiveActionResult = ready && typeof activatePrototypeObjectiveAction === 'function'
    ? activatePrototypeObjectiveAction()
    : false;
  const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
  const completionNotice = window.HWANSE_LAST_FIELD_STATUS_COMPLETION_NOTICE || null;
  if (typeof render === 'function') render();
  const noticeFeedbackLog = [...(window.HWANSE_FIELD_STATUS_FEEDBACK_LOG || [])];
  const noticeFeedbackRender = [...(window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER || [])];
  const noticeFeedbackLast = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK || null;
  const noticeFeedbackLastRender = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER || null;
  const playHud = typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []);
  window.__hwanseFieldPoisonEffectContinue = {
    ok: ready,
    moved,
    titleReturned,
    continueIndex,
    titleItemsBefore: titleItemsBefore.map((item) => ({ key: item.key, label: item.label })),
    scene,
    map: map?.name || '',
    foot: typeof footTile === 'function' ? footTile() : null,
    quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
    effectBeforeTitle,
    fieldStatusAutoSave,
    savedBeforeTitle,
    savedBeforeTitleAtaho: savedBeforeTitleAtaho ? {
      name: savedBeforeTitleAtaho.name,
      hp: savedBeforeTitleAtaho.hp,
      hpMax: savedBeforeTitleAtaho.hpMax,
      statuses: [...(savedBeforeTitleAtaho.statuses || [])],
      fieldPoisonStepCount: savedBeforeTitleAtaho.fieldPoisonStepCount || 0,
    } : null,
    objectiveBeforeTitle,
    objectiveAfterContinue,
    objectiveActionResult,
    objectiveAction,
    completionNotice,
    fieldStatusNoticeFeedbackLog: noticeFeedbackLog,
    fieldStatusNoticeFeedbackRender: noticeFeedbackRender,
    fieldStatusNoticeFeedbackLast: noticeFeedbackLast,
    fieldStatusNoticeFeedbackLastRender: noticeFeedbackLastRender,
    restoredAtaho: restoredAtaho ? {
      name: restoredAtaho.name,
      hp: restoredAtaho.hp,
      hpMax: restoredAtaho.hpMax,
      statuses: [...(restoredAtaho.statuses || [])],
      fieldPoisonStepCount: restoredAtaho.fieldPoisonStepCount || 0,
    } : null,
    playHudLines: playHud,
    originalStatusFormulaImplemented: false,
    originalStoryFlagRuntimeImplemented: false,
  };
}
finishEffectContinueWhenReady();
return true;
"""


def field_poison_effect_continue_state_script() -> str:
    return """
return window.__hwanseFieldPoisonEffectContinue || {
  pending: true,
  scene: typeof scene === 'undefined' ? '' : scene,
  map: typeof map === 'undefined' || !map ? '' : map.name,
  titleItems: typeof titleMenuItems === 'function' ? titleMenuItems().map((item) => item.label) : [],
};
"""


def field_paralysis_movement_script() -> str:
    return """
window.__hwanseFieldParalysisMovement = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
activeDialogue = null;
menuOpen = false;
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
runtimeState = createPrototypeRuntimeState();
const ataho = (runtimeState.characters || []).find((member) => member.name === 'Ataho');
ataho.hp = 36;
ataho.hpMax = 36;
ataho.statuses = ['paralysis'];
ataho.fieldPoisonStepCount = 0;
ataho.fieldParalysisMoveCount = 0;
{
  const position = playerPositionForTile(11, 12);
  setPlayerPosition(position.x, position.y);
}
player.dir = 2;
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
fieldEncounterState = createFieldEncounterState({ enabled: false });
lastMovementAction = null;
pendingMovementAction = null;
virtualMovementAction = null;
window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_BLOCK = null;
window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_BLOCK_AUTO_SAVE = null;
window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_STEP = null;
window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_STEP_AUTO_SAVE = null;
window.HWANSE_FIELD_STATUS_FEEDBACK_LOG = [];
window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER = [];
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK = null;
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER = null;
activeFieldStatusFeedbacks = [];

const attempts = [];
function readSavedPayloadForParalysis() {
  try {
    return JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
  } catch (error) {
    return { error: String(error && error.message || error) };
  }
}
function attemptParalysisMove(dx, dy) {
  const beforeFoot = footTile();
  const beforeCounter = ataho.fieldParalysisMoveCount || 0;
  const movement = movementTargetFromTile(beforeFoot, dx, dy);
  if (!movement) {
    attempts.push({ error: "blocked-by-map", beforeFoot, dx, dy });
    return false;
  }
  const movementStatus = applyFieldMovementStatusBeforeStep(movement);
  if (movementStatus?.blocked) {
    update(0);
    render();
    const savedAfterAttempt = readSavedPayloadForParalysis();
    const savedAfterAttemptAtaho = (savedAfterAttempt?.runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
    attempts.push({
      beforeFoot,
      afterFoot: footTile(),
      dx,
      dy,
      moved: false,
      blocked: true,
      beforeCounter,
      afterCounter: ataho.fieldParalysisMoveCount || 0,
      movementStatus,
      movementBlockAutoSave: window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_BLOCK_AUTO_SAVE || null,
      savedAfterAttemptAtaho: savedAfterAttemptAtaho ? {
        hp: savedAfterAttemptAtaho.hp,
        statuses: [...(savedAfterAttemptAtaho.statuses || [])],
        fieldParalysisMoveCount: savedAfterAttemptAtaho.fieldParalysisMoveCount || 0,
      } : null,
    });
    return true;
  }
  recordPartyTrailPosition(movement.target);
  player.step = {
    fromX: player.x,
    fromY: player.y,
    toX: movement.target.x,
    toY: movement.target.y,
    elapsed: 0,
    duration: TILE_STEP_SECONDS,
    walkScriptStartPhase: player.walkScriptPhase,
    fieldMovementStatus: movementStatus,
  };
  for (let index = 0; index < 4; index += 1) update(ORIGINAL_FRAME_SECONDS);
  update(0);
  attempts.push({
    beforeFoot,
    afterFoot: footTile(),
    dx,
    dy,
    moved: true,
    blocked: false,
    beforeCounter,
    afterCounter: ataho.fieldParalysisMoveCount || 0,
    movementStatus,
  });
  return true;
}

const ok =
  attemptParalysisMove(1, 0) &&
  attemptParalysisMove(-1, 0) &&
  attemptParalysisMove(1, 0);
const movementBlock = window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_BLOCK || null;
const movementBlockAutoSave = window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_BLOCK_AUTO_SAVE || null;
const movementFeedbackLog = window.HWANSE_FIELD_STATUS_FEEDBACK_LOG || [];
const movementFeedbackRender = window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER || [];
const movementFeedbackLast = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK || null;
const movementFeedbackLastRender = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER || null;
let savedBeforeTitle = readSavedPayloadForParalysis();
const savedBeforeTitleAtaho = (savedBeforeTitle?.runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
const objectiveBeforeTitle = typeof prototypeObjectiveState === 'function' ? prototypeObjectiveState() : null;
const titleReturned = returnToTitleScreen();
const titleItemsBefore = titleMenuItems();
const continueIndex = titleItemsBefore.findIndex((item) => item.key === 'continue');
if (ok && titleReturned && continueIndex >= 0) {
  selectedTitleMenuIndex = continueIndex;
  activateSelectedTitleMenuItem();
}
const deadline = performance.now() + 3000;
function finishParalysisMovementWhenReady() {
  const restoredAtaho = (runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
  const objectiveAfterContinue = typeof prototypeObjectiveState === 'function' ? prototypeObjectiveState() : null;
  const ready = (
    ok &&
    titleReturned &&
    continueIndex >= 0 &&
    scene === 'map' &&
    map?.name === 'map1_02b' &&
    restoredAtaho &&
    restoredAtaho.hp === 36 &&
    (restoredAtaho.statuses || []).includes('paralysis') &&
    (restoredAtaho.fieldParalysisMoveCount || 0) === 0 &&
    objectiveAfterContinue?.title === '후보 필드 상태 완료 map1_02b'
  );
  if (!ready && performance.now() < deadline) {
    requestAnimationFrame(finishParalysisMovementWhenReady);
    return;
  }
  window.HWANSE_LAST_FIELD_STATUS_COMPLETION_NOTICE = null;
  window.HWANSE_LAST_OBJECTIVE_ACTION = null;
  const objectiveActionResult = ready && typeof activatePrototypeObjectiveAction === 'function'
    ? activatePrototypeObjectiveAction()
    : false;
  const objectiveAction = window.HWANSE_LAST_OBJECTIVE_ACTION || null;
  const completionNotice = window.HWANSE_LAST_FIELD_STATUS_COMPLETION_NOTICE || null;
  if (typeof render === 'function') render();
  const noticeFeedbackLog = [...(window.HWANSE_FIELD_STATUS_FEEDBACK_LOG || [])];
  const noticeFeedbackRender = [...(window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER || [])];
  const noticeFeedbackLast = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK || null;
  const noticeFeedbackLastRender = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER || null;
  const playHud = typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []);
  const statusMenuState = typeof partyStatusMenuState === 'function' ? partyStatusMenuState() : null;
  const statusMenuLines = typeof statusMenuReviewBlock === 'function' ? statusMenuReviewBlock(statusMenuState).lines : [];
  window.__hwanseFieldParalysisMovement = {
    ok: ready,
    attempts,
    titleReturned,
    continueIndex,
    titleItemsBefore: titleItemsBefore.map((item) => ({ key: item.key, label: item.label })),
    scene,
    map: map?.name || '',
    foot: typeof footTile === 'function' ? footTile() : null,
    quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
    movementBlock,
    movementBlockAutoSave,
    movementFeedbackLog,
    movementFeedbackRender,
    movementFeedbackLast,
    movementFeedbackLastRender,
    savedBeforeTitle,
    savedBeforeTitleAtaho: savedBeforeTitleAtaho ? {
      name: savedBeforeTitleAtaho.name,
      hp: savedBeforeTitleAtaho.hp,
      hpMax: savedBeforeTitleAtaho.hpMax,
      statuses: [...(savedBeforeTitleAtaho.statuses || [])],
      fieldParalysisMoveCount: savedBeforeTitleAtaho.fieldParalysisMoveCount || 0,
    } : null,
    objectiveBeforeTitle,
    objectiveAfterContinue,
    objectiveActionResult,
    objectiveAction,
    completionNotice,
    fieldStatusNoticeFeedbackLog: noticeFeedbackLog,
    fieldStatusNoticeFeedbackRender: noticeFeedbackRender,
    fieldStatusNoticeFeedbackLast: noticeFeedbackLast,
    fieldStatusNoticeFeedbackLastRender: noticeFeedbackLastRender,
    restoredAtaho: restoredAtaho ? {
      name: restoredAtaho.name,
      hp: restoredAtaho.hp,
      hpMax: restoredAtaho.hpMax,
      statuses: [...(restoredAtaho.statuses || [])],
      fieldParalysisMoveCount: restoredAtaho.fieldParalysisMoveCount || 0,
    } : null,
    playHudLines: playHud,
    statusMenuLines,
    originalStatusFormulaImplemented: false,
    originalStoryFlagRuntimeImplemented: false,
  };
}
finishParalysisMovementWhenReady();
return true;
"""


def field_paralysis_movement_state_script() -> str:
    return """
return window.__hwanseFieldParalysisMovement || {
  pending: true,
  scene: typeof scene === 'undefined' ? '' : scene,
  map: typeof map === 'undefined' || !map ? '' : map.name,
  titleItems: typeof titleMenuItems === 'function' ? titleMenuItems().map((item) => item.label) : [],
};
"""


def field_paralysis_partial_continue_script() -> str:
    return """
window.__hwanseFieldParalysisPartialContinue = null;
localStorage.removeItem(RUNTIME_SAVE_KEY);
activeDialogue = null;
menuOpen = false;
prototypeProgress = createPrototypeProgressState();
publishPrototypeProgress();
publishPrototypeCompletionState();
runtimeState = createPrototypeRuntimeState();
const ataho = (runtimeState.characters || []).find((member) => member.name === 'Ataho');
ataho.hp = 36;
ataho.hpMax = 36;
ataho.statuses = ['paralysis'];
ataho.fieldParalysisMoveCount = 0;
{
  const position = playerPositionForTile(11, 12);
  setPlayerPosition(position.x, position.y);
}
player.dir = 2;
player.walkScriptPhase = 0;
player.frame = PLAYER_IDLE_FRAME;
player.step = null;
fieldEncounterState = createFieldEncounterState({ enabled: false });
lastMovementAction = null;
pendingMovementAction = null;
virtualMovementAction = null;
window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_STEP = null;
window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_STEP_AUTO_SAVE = null;
window.HWANSE_FIELD_STATUS_FEEDBACK_LOG = [];
window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER = [];
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK = null;
window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER = null;
activeFieldStatusFeedbacks = [];

function moveParalysisPartial(dx, dy) {
  const beforeFoot = footTile();
  const movement = movementTargetFromTile(beforeFoot, dx, dy);
  if (!movement) return false;
  const fieldMovementStatus = applyFieldMovementStatusBeforeStep(movement);
  if (fieldMovementStatus?.blocked) return false;
  recordPartyTrailPosition(movement.target);
  player.step = {
    fromX: player.x,
    fromY: player.y,
    toX: movement.target.x,
    toY: movement.target.y,
    elapsed: 0,
    duration: TILE_STEP_SECONDS,
    walkScriptStartPhase: player.walkScriptPhase,
    fieldMovementStatus,
  };
  for (let index = 0; index < 4; index += 1) update(ORIGINAL_FRAME_SECONDS);
  update(0);
  return true;
}

const moved =
  moveParalysisPartial(1, 0) &&
  moveParalysisPartial(-1, 0);
if (typeof render === 'function') render();
const movementFeedbackLog = window.HWANSE_FIELD_STATUS_FEEDBACK_LOG || [];
const movementFeedbackRender = window.HWANSE_FIELD_STATUS_FEEDBACK_RENDER || [];
const movementFeedbackLast = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK || null;
const movementFeedbackLastRender = window.HWANSE_LAST_FIELD_STATUS_FEEDBACK_RENDER || null;
let savedBeforeTitle = null;
try {
  savedBeforeTitle = JSON.parse(localStorage.getItem(RUNTIME_SAVE_KEY) || "null");
} catch (error) {
  savedBeforeTitle = { error: String(error && error.message || error) };
}
const savedBeforeTitleAtaho = (savedBeforeTitle?.runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
const movementStep = window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_STEP || null;
const movementStepAutoSave = window.HWANSE_LAST_FIELD_STATUS_MOVEMENT_STEP_AUTO_SAVE || null;
const titleReturned = returnToTitleScreen();
const titleItemsBefore = titleMenuItems();
const continueIndex = titleItemsBefore.findIndex((item) => item.key === 'continue');
if (moved && titleReturned && continueIndex >= 0) {
  selectedTitleMenuIndex = continueIndex;
  activateSelectedTitleMenuItem();
}
const deadline = performance.now() + 3000;
function finishParalysisPartialContinueWhenReady() {
  const restoredAtaho = (runtimeState?.characters || []).find((member) => member.name === 'Ataho') || null;
  const playHud = typeof playHudLines === 'function' ? playHudLines() : (window.HWANSE_LAST_PLAY_HUD_LINES || []);
  const statusMenuState = typeof partyStatusMenuState === 'function' ? partyStatusMenuState() : null;
  const statusMenuLines = typeof statusMenuReviewBlock === 'function' ? statusMenuReviewBlock(statusMenuState).lines : [];
  const ready = (
    moved &&
    titleReturned &&
    continueIndex >= 0 &&
    scene === 'map' &&
    map?.name === 'map1_02b' &&
    restoredAtaho &&
    restoredAtaho.hp === 36 &&
    (restoredAtaho.statuses || []).includes('paralysis') &&
    (restoredAtaho.fieldParalysisMoveCount || 0) === 2
  );
  if (!ready && performance.now() < deadline) {
    requestAnimationFrame(finishParalysisPartialContinueWhenReady);
    return;
  }
  window.__hwanseFieldParalysisPartialContinue = {
    ok: ready,
    moved,
    titleReturned,
    continueIndex,
    titleItemsBefore: titleItemsBefore.map((item) => ({ key: item.key, label: item.label })),
    scene,
    map: map?.name || '',
    foot: typeof footTile === 'function' ? footTile() : null,
    quickLoadText: document.getElementById('quickLoadButton')?.textContent || '',
    savedBeforeTitle,
    savedBeforeTitleAtaho: savedBeforeTitleAtaho ? {
      name: savedBeforeTitleAtaho.name,
      hp: savedBeforeTitleAtaho.hp,
      hpMax: savedBeforeTitleAtaho.hpMax,
      statuses: [...(savedBeforeTitleAtaho.statuses || [])],
      fieldParalysisMoveCount: savedBeforeTitleAtaho.fieldParalysisMoveCount || 0,
    } : null,
    movementStep,
    movementStepAutoSave,
    movementFeedbackLog,
    movementFeedbackRender,
    movementFeedbackLast,
    movementFeedbackLastRender,
    statusMenuState,
    statusMenuLines,
    restoredAtaho: restoredAtaho ? {
      name: restoredAtaho.name,
      hp: restoredAtaho.hp,
      hpMax: restoredAtaho.hpMax,
      statuses: [...(restoredAtaho.statuses || [])],
      fieldParalysisMoveCount: restoredAtaho.fieldParalysisMoveCount || 0,
    } : null,
    playHudLines: playHud,
    originalStatusFormulaImplemented: false,
    originalStoryFlagRuntimeImplemented: false,
  };
}
finishParalysisPartialContinueWhenReady();
return true;
"""


def field_paralysis_partial_continue_state_script() -> str:
    return """
return window.__hwanseFieldParalysisPartialContinue || {
  pending: true,
  scene: typeof scene === 'undefined' ? '' : scene,
  map: typeof map === 'undefined' || !map ? '' : map.name,
  titleItems: typeof titleMenuItems === 'function' ? titleMenuItems().map((item) => item.label) : [],
};
"""


def wait_for_field_poison_effect_continue(port: int, session_id: str, timeout: float = 6) -> dict:
    deadline = time.monotonic() + timeout
    last_state: dict = {}
    while time.monotonic() < deadline:
        state = execute_js(port, session_id, field_poison_effect_continue_state_script(), timeout=3)
        if isinstance(state, dict):
            last_state = state
            if not state.get("pending"):
                return state
        time.sleep(0.2)
    raise WebDriverError(f"field poison effect title continue did not finish: {last_state!r}")


def wait_for_field_paralysis_movement(port: int, session_id: str, timeout: float = 6) -> dict:
    deadline = time.monotonic() + timeout
    last_state: dict = {}
    while time.monotonic() < deadline:
        state = execute_js(port, session_id, field_paralysis_movement_state_script(), timeout=3)
        if isinstance(state, dict):
            last_state = state
            if not state.get("pending"):
                return state
        time.sleep(0.2)
    raise WebDriverError(f"field paralysis movement did not finish: {last_state!r}")


def wait_for_field_paralysis_partial_continue(port: int, session_id: str, timeout: float = 6) -> dict:
    deadline = time.monotonic() + timeout
    last_state: dict = {}
    while time.monotonic() < deadline:
        state = execute_js(port, session_id, field_paralysis_partial_continue_state_script(), timeout=3)
        if isinstance(state, dict):
            last_state = state
            if not state.get("pending"):
                return state
        time.sleep(0.2)
    raise WebDriverError(f"field paralysis partial title continue did not finish: {last_state!r}")


def wait_for_field_poison_cure_continue(port: int, session_id: str, timeout: float = 6) -> dict:
    deadline = time.monotonic() + timeout
    last_state: dict = {}
    while time.monotonic() < deadline:
        state = execute_js(port, session_id, field_poison_cure_continue_state_script(), timeout=3)
        if isinstance(state, dict):
            last_state = state
            if not state.get("pending"):
                return state
        time.sleep(0.2)
    raise WebDriverError(f"field poison cure title continue did not finish: {last_state!r}")


def wait_for_field_paralysis_cure_continue(port: int, session_id: str, timeout: float = 6) -> dict:
    deadline = time.monotonic() + timeout
    last_state: dict = {}
    while time.monotonic() < deadline:
        state = execute_js(port, session_id, field_paralysis_cure_continue_state_script(), timeout=3)
        if isinstance(state, dict):
            last_state = state
            if not state.get("pending"):
                return state
        time.sleep(0.2)
    raise WebDriverError(f"field paralysis cure title continue did not finish: {last_state!r}")


def wait_for_field_poison_partial_continue(port: int, session_id: str, timeout: float = 6) -> dict:
    deadline = time.monotonic() + timeout
    last_state: dict = {}
    while time.monotonic() < deadline:
        state = execute_js(port, session_id, field_poison_partial_continue_state_script(), timeout=3)
        if isinstance(state, dict):
            last_state = state
            if not state.get("pending"):
                return state
        time.sleep(0.2)
    raise WebDriverError(f"field poison partial title continue did not finish: {last_state!r}")


def verify_trace(state: dict) -> None:
    if state.get("error"):
        raise WebDriverError(f"candidate movement trace failed: {state!r}")
    samples = state.get("samples") or []
    expected_positions = [
        ("start", 168, 176, {"x": 11, "y": 12}, 0, False, 0, True, 0, 3, {"x": 240, "y": 64, "width": 48, "height": 64}),
        ("tick1", 184, 176, {"x": 12, "y": 12}, 0, True, 0, False, None, 16, {"x": 288, "y": 64, "width": 48, "height": 64}),
        ("tick2", 184, 176, {"x": 12, "y": 12}, 0, False, 0, False, None, 3, {"x": 240, "y": 64, "width": 48, "height": 64}),
        ("tick3", 184, 176, {"x": 12, "y": 12}, 0, False, 0, False, None, 3, {"x": 240, "y": 64, "width": 48, "height": 64}),
        ("tick4", 184, 176, {"x": 12, "y": 12}, 0, False, 0, False, None, 3, {"x": 240, "y": 64, "width": 48, "height": 64}),
        ("idle-after-complete", 184, 176, {"x": 12, "y": 12}, 0, False, 0, False, None, 3, {"x": 240, "y": 64, "width": 48, "height": 64}),
    ]
    if (
        state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or state.get("error")
        or state.get("originalFrameMs") != 48
        or abs(float(state.get("originalFrameSeconds") or 0) - 0.048) > 0.000001
        or state.get("defaultTileStepMs") != 48
        or abs(float(state.get("tileStepSeconds") or 0) - 0.048) > 0.000001
        or state.get("playerOriginalTileStepCommands") != 1
        or state.get("playerOriginalWalkCadenceCommands") != 8
        or state.get("playerOriginalWalkScriptSelectors") != [16, 17, 16, 3, 18, 19, 18, 3]
        or state.get("playerOriginalIdleSelector") != 3
        or state.get("playerWalkFrames") != 5
        or state.get("playerFirstWalkFrame") != 1
        or state.get("playerFootprintRule") != "bottom-3x1-horizontal-footprint"
        or state.get("playerFootTileSize") != 16
        or state.get("playerFootTileCenterOffsetX") != 16
        or state.get("playerFootTileBottomOffsetY") != 24
        or state.get("playerTilePositionOffset") != {"x": 8, "y": 16}
        or state.get("originalProjectionBias") != {"x": 24, "y": 8}
        or state.get("originalProjectionFixedShift") != 16
        or state.get("originalProjectionFixedScale") != 65536
        or state.get("originalProjectionTopOffset") != {"x": 40, "y": 56}
        or state.get("startTile") != {"x": 11, "y": 12}
        or state.get("targetTile") != {"x": 12, "y": 12}
        or len(samples) != len(expected_positions)
    ):
        raise WebDriverError(f"candidate movement trace metadata is incomplete: {state!r}")
    for sample, expected in zip(samples, expected_positions, strict=True):
        label, x, y, foot, frame, moving, phase, active, elapsed, selector, source_rect = expected
        if (
            sample.get("label") != label
            or abs(float(sample.get("x") or 0) - x) > 0.001
            or abs(float(sample.get("y") or 0) - y) > 0.001
            or sample.get("foot") != foot
            or sample.get("footprintTiles") != [
                {"x": foot["x"] - 1, "y": foot["y"]},
                {"x": foot["x"], "y": foot["y"]},
                {"x": foot["x"] + 1, "y": foot["y"]},
            ]
            or sample.get("frame") != frame
            or sample.get("moving") is not moving
            or sample.get("walkScriptPhase") != phase
            or sample.get("stepActive") is not active
            or sample.get("selector") != selector
            or sample.get("originalSelector") != selector
            or sample.get("sourceRect") != source_rect
            or sample.get("expectedSourceRect") != source_rect
            or sample.get("sourceMatchesSelector") is not True
            or sample.get("projectionMatchesDraw") is not True
        ):
            raise WebDriverError(f"candidate movement trace sample mismatch: expected={expected!r} sample={sample!r} state={state!r}")
        projection = sample.get("projection") or {}
        interpolation_x = x - (foot["x"] * 16 - 8)
        interpolation_y = y - (foot["y"] * 16 - 16)
        projected_x = foot["x"] * 16 + 24
        projected_y = foot["y"] * 16 + 8
        projected_top_x = x - 8
        projected_top_y = y - 32
        if (
            projection.get("source") != "original-draw-projection-snapshot"
            or projection.get("objectTileFields") != "+0xe8/+0xea"
            or projection.get("cameraTileGlobals") != "0x004576dc/0x004576de"
            or projection.get("projectedCoordFields") != "+0x1c/+0x20"
            or projection.get("tileSize") != 16
            or projection.get("biasX") != 24
            or projection.get("biasY") != 8
            or projection.get("fixedPointShift") != 16
            or projection.get("fixedPointScale") != 65536
            or projection.get("footprintRule") != "bottom-3x1-horizontal-footprint"
            or projection.get("footTileSize") != 16
            or projection.get("footTileCenterOffsetX") != 16
            or projection.get("footTileBottomOffsetY") != 24
            or projection.get("tile") != foot
            or projection.get("cameraTile") != {"x": 0, "y": 0, "subPixelX": 0, "subPixelY": 0}
            or projection.get("interpolationOffset") != {"x": interpolation_x, "y": interpolation_y}
            or projection.get("projectedScreen") != {"x": projected_x, "y": projected_y}
            or projection.get("adjustedProjectedScreen") != {"x": projected_x, "y": projected_y}
            or projection.get("interpolatedProjectedScreen") != {
                "x": projected_x + interpolation_x,
                "y": projected_y + interpolation_y,
            }
            or projection.get("fixedPoint") != {"x": projected_x * 65536, "y": projected_y * 65536}
            or projection.get("webProjectedSpriteTop") != {"x": projected_top_x, "y": projected_top_y}
            or projection.get("actualSpriteTop") != {"x": projected_top_x, "y": projected_top_y}
            or projection.get("webProjectionMatchesDraw") is not True
            or projection.get("browserOriginalProjectionSnapshotImplemented") is not True
            or projection.get("originalActorInterpolationImplemented") is not False
            or sample.get("renderScreen") != {"x": projected_top_x, "y": projected_top_y}
        ):
            raise WebDriverError(f"candidate movement projection mismatch: expected={expected!r} sample={sample!r} projection={projection!r}")
        sample_elapsed = sample.get("stepElapsed")
        if elapsed is None:
            if sample_elapsed is not None:
                raise WebDriverError(f"candidate movement trace expected no active elapsed: {sample!r}")
        elif abs(float(sample_elapsed or 0) - elapsed) > 0.000001:
            raise WebDriverError(f"candidate movement trace elapsed mismatch: expected={elapsed!r} sample={sample!r}")


def verify_movement_step_sound(state: dict) -> None:
    sound = state.get("fieldMovementSound") or {}
    sound_last = state.get("fieldMovementSoundLast") or {}
    sound_log = state.get("fieldMovementSoundLog") or []
    last_sound = state.get("lastSound") or {}
    if (
        state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or state.get("beforeFoot") != {"x": 11, "y": 12}
        or state.get("afterFoot") != {"x": 12, "y": 12}
        or state.get("defaultPrevented") is not True
        or state.get("keyReleased") is not True
        or state.get("stepActiveAfterInput") is not True
        or state.get("stepActiveAfterComplete") is not False
        or state.get("fieldMovementSoundLogLength") != 1
        or state.get("fieldMovementSoundLogLimit") != 48
        or int((state.get("soundCounts") or {}).get("step") or 0) != 1
        or last_sound.get("key") != "step"
        or last_sound.get("src") != "../extract_wlk/00.wav"
        or sound != sound_last
        or not sound_log
        or (sound_log[-1] or {}) != sound
        or sound.get("source") != "field-movement-sound"
        or sound.get("inputSource") != "keyboard-movement"
        or sound.get("movementAction") != 1
        or sound.get("dx") != 1
        or sound.get("dy") != 0
        or sound.get("direction") != "right"
        or sound.get("fromTile") != {"x": 11, "y": 12}
        or sound.get("targetTile") != {"x": 12, "y": 12}
        or sound.get("soundKey") != "step"
        or sound.get("soundSrc") != "../extract_wlk/00.wav"
        or sound.get("soundPlayed") is not True
        or sound.get("playerStepActive") is not True
        or sound.get("browserFieldMovementSoundImplemented") is not True
        or sound.get("originalStepSoundTimingImplemented") is not False
        or sound.get("originalActorMovementRuntimeImplemented") is not False
        or sound.get("originalStoryFlagRuntimeImplemented") is not False
        or state.get("originalStepSoundTimingImplemented") is not False
        or state.get("originalActorMovementRuntimeImplemented") is not False
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate movement step sound is incomplete: {state!r}")


def verify_blocked_movement_bump(state: dict) -> None:
    if state.get("error"):
        raise WebDriverError(f"candidate movement blocked bump failed: {state!r}")
    attempt = state.get("attempt") or {}
    direction = attempt.get("direction") or {}
    tile = attempt.get("tile") or {}
    target_tile = attempt.get("targetTile") or {}
    effect = state.get("effect") or {}
    render = state.get("render") or {}
    render_list = state.get("renderList") or []
    bump_sound = state.get("bumpSound") or {}
    bump_sound_log = state.get("bumpSoundLog") or []
    bump_sound_first = bump_sound_log[0] if bump_sound_log else {}
    sound_counts = state.get("soundCounts") or {}
    last_sound = state.get("lastSound") or {}
    if (
        state.get("ok") is not True
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or state.get("beforeFoot") != tile
        or state.get("afterFoot") != tile
        or state.get("playerStepActive") is not False
        or state.get("playerFrame") != 0
        or state.get("activeMovementBumpEffect") is not True
        or state.get("logLength") != 1
        or state.get("bumpSoundLogLength") != 1
        or state.get("movementBumpSoundLogLimit") != 48
        or sound_counts.get("menuMove") != 1
        or last_sound.get("key") != "menuMove"
        or last_sound.get("src") != "../extract_wlk/03.wav"
        or state.get("movementBumpDurationMs") != 220
        or state.get("movementBumpLogLimit") != 32
        or target_tile == tile
        or effect.get("source") != "movement-collision"
        or effect.get("reason") != "collision"
        or effect.get("map") != "map1_02b"
        or effect.get("dx") != direction.get("dx")
        or effect.get("dy") != direction.get("dy")
        or effect.get("fromTile") != tile
        or effect.get("targetTile") != target_tile
        or effect.get("durationMs") != 220
        or (effect.get("sound") or {}).get("source") != "movement-bump-sound"
        or (effect.get("sound") or {}).get("soundSrc") != "../extract_wlk/03.wav"
        or effect.get("browserMovementBumpFeedbackImplemented") is not True
        or effect.get("originalActorInterpolationImplemented") is not False
        or effect.get("originalObjectScriptCollisionRuntimeImplemented") is not False
        or effect.get("originalStoryFlagRuntimeImplemented") is not False
        or render.get("source") != "movement-collision"
        or render.get("reason") != "collision"
        or render.get("targetTile") != target_tile
        or render.get("durationMs") != 220
        or render.get("active") is not True
        or render.get("browserMovementBumpFeedbackImplemented") is not True
        or not render_list
        or float(render.get("alpha") or 0) <= 0
        or bump_sound.get("source") != "movement-bump-sound"
        or bump_sound.get("bumpSource") != "movement-collision"
        or bump_sound.get("reason") != "collision"
        or bump_sound.get("map") != "map1_02b"
        or bump_sound.get("fromTile") != tile
        or bump_sound.get("targetTile") != target_tile
        or bump_sound.get("soundKey") != "menuMove"
        or bump_sound.get("soundSrc") != "../extract_wlk/03.wav"
        or bump_sound.get("soundPlayed") is not True
        or bump_sound.get("playerStepActive") is not False
        or bump_sound.get("browserMovementBumpSoundImplemented") is not True
        or bump_sound.get("browserMovementBumpFeedbackImplemented") is not True
        or bump_sound.get("originalObjectScriptCollisionRuntimeImplemented") is not False
        or bump_sound.get("originalStoryFlagRuntimeImplemented") is not False
        or bump_sound_first.get("source") != "movement-bump-sound"
        or bump_sound_first.get("soundSrc") != "../extract_wlk/03.wav"
        or state.get("originalActorInterpolationImplemented") is not False
        or state.get("originalObjectScriptCollisionRuntimeImplemented") is not False
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate movement blocked bump is incomplete: {state!r}")


def verify_wall_slide_direction(state: dict) -> None:
    if state.get("error"):
        raise WebDriverError(f"candidate movement wall-slide direction failed: {state!r}")
    attempt = state.get("attempt") or {}
    tile = attempt.get("tile") or {}
    input_direction = attempt.get("input") or {}
    movement = attempt.get("movement") or {}
    next_tile = movement.get("nextTile") or {}
    target = movement.get("target") or {}
    step = state.get("stepAfterInput") or {}
    trail = state.get("trailEntry") or {}
    sound = state.get("fieldMovementSound") or {}
    sound_counts = state.get("soundCounts") or {}
    last_sound = state.get("lastSound") or {}
    if (
        state.get("ok") is not True
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or not tile
        or not input_direction
        or not movement
        or state.get("stepActiveAfterInput") is not True
        or state.get("stepActiveAfterComplete") is not False
        or state.get("playerDirAfterInput") != state.get("expectedDir")
        or state.get("actualPlayerDir") != state.get("expectedDir")
        or state.get("inputDir") == state.get("expectedDir")
        or (input_direction.get("dx") == movement.get("dx") and input_direction.get("dy") == movement.get("dy"))
        or (state.get("afterFoot") or {}) != next_tile
        or step.get("toX") != target.get("x")
        or step.get("toY") != target.get("y")
        or (trail and trail.get("dir") != state.get("expectedDir"))
        or sound.get("source") != "field-movement-sound"
        or sound.get("movementAction") != state.get("expectedAction")
        or sound.get("dx") != movement.get("dx")
        or sound.get("dy") != movement.get("dy")
        or sound.get("fromTile") != tile
        or sound.get("targetTile") != next_tile
        or sound.get("soundKey") != "step"
        or sound.get("soundSrc") not in {"../extract_wlk/00.wav", "../extract_wlk/01.wav"}
        or sound.get("soundPlayed") is not True
        or sound.get("browserFieldMovementSoundImplemented") is not True
        or sound_counts.get("step") != 1
        or last_sound.get("key") != "step"
        or state.get("wallSlideActualDirectionImplemented") is not True
        or state.get("originalWallSlideFacingProofImplemented") is not False
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate movement wall-slide direction is incomplete: {state!r}")


def verify_party_trail_animation(state: dict) -> None:
    samples = state.get("samples") or []
    lagging_turn = state.get("laggingTurnSample") or {}
    lagging_followers = ((lagging_turn.get("mid") or {}).get("followers") or [])
    if (
        state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or state.get("partyMembers") != ["rinshan", "smash"]
        or state.get("actorCollisionEnabled") is not False
        or state.get("overlapMoveAllowed") is not True
        or state.get("partyActorsPassThroughByDefault") is not True
        or state.get("partyFollowerSegmentInterpolationImplemented") is not True
        or len(samples) != 8
        or any(sample.get("error") for sample in samples)
        or state.get("followerMovingSampleCount", 0) < 2
        or not lagging_turn
        or not any(actor.get("moving") is True and actor.get("dir") != lagging_turn.get("dir") for actor in lagging_followers)
        or not any((sample.get("mid") or {}).get("followerSteps") for sample in samples)
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate movement party trail animation is incomplete: {state!r}")


def verify_field_poison_trace(state: dict) -> None:
    steps = state.get("movementSteps") or []
    effect = state.get("fieldStatusEffect") or {}
    effects = effect.get("effects") or []
    poison = effects[0] if effects else {}
    auto_save = state.get("fieldStatusAutoSave") or {}
    auto_effects = auto_save.get("effects") or []
    auto_poison = auto_effects[0] if auto_effects else {}
    progress = state.get("progress") or {}
    counts = progress.get("counts") or {}
    ataho = state.get("ataho") or {}
    saved_ataho = state.get("savedAtaho") or {}
    saved_payload = state.get("savedPayload") or {}
    play_hud = " ".join(str(line) for line in (state.get("playHudLines") or []))
    objective_before = state.get("objectiveBefore") or {}
    objective_action = state.get("objectiveAction") or {}
    completion_notice = state.get("completionNotice") or {}
    completion_lines = " ".join(str(line) for line in (completion_notice.get("lines") or []))
    feedback = state.get("fieldStatusFeedbackLast") or {}
    feedback_render = state.get("fieldStatusFeedbackLastRender") or {}
    feedback_log = state.get("fieldStatusFeedbackLog") or []
    feedback_render_list = state.get("fieldStatusFeedbackRender") or []
    notice_feedback = state.get("fieldStatusNoticeFeedbackLast") or {}
    notice_feedback_render = state.get("fieldStatusNoticeFeedbackLastRender") or {}
    notice_feedback_log = state.get("fieldStatusNoticeFeedbackLog") or []
    notice_feedback_render_list = state.get("fieldStatusNoticeFeedbackRender") or []
    completion_notice_feedback = completion_notice.get("feedback") or {}
    effect_feedback = effect.get("statusFeedback") or {}
    auto_feedback = auto_save.get("statusFeedback") or {}
    step2 = steps[1] if len(steps) > 1 else {}
    step2_status_step = step2.get("fieldStatusStep") or {}
    step2_counters = step2_status_step.get("counters") or []
    step2_counter = step2_counters[0] if step2_counters else {}
    step2_auto_save = step2.get("fieldStatusStepAutoSave") or {}
    step2_saved_ataho = step2.get("savedAfterStepAtaho") or {}
    if (
        state.get("ok") is not True
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or state.get("fieldPoisonStepInterval") != 4
        or len(steps) != 4
        or steps[0].get("afterHp") != 36
        or steps[0].get("afterCounter") != 1
        or steps[1].get("afterHp") != 36
        or steps[1].get("afterCounter") != 2
        or step2_status_step.get("source") != "prototype-field-status-step"
        or step2_status_step.get("counterCount") != 1
        or step2_counter.get("key") != "poison"
        or step2_counter.get("stepCount") != 2
        or step2_counter.get("stepInterval") != 4
        or step2_auto_save.get("saved") is not True
        or step2_auto_save.get("source") != "field-status-step-prototype"
        or step2_auto_save.get("scope") != "field-status-step"
        or step2_saved_ataho.get("hp") != 36
        or "poison" not in (step2_saved_ataho.get("statuses") or [])
        or step2_saved_ataho.get("fieldPoisonStepCount") != 2
        or steps[2].get("afterHp") != 36
        or steps[2].get("afterCounter") != 3
        or steps[3].get("beforeHp") != 36
        or steps[3].get("afterHp") != 34
        or steps[3].get("afterCounter") != 0
        or ataho.get("name") != "Ataho"
        or ataho.get("hp") != 34
        or "poison" not in (ataho.get("statuses") or [])
        or effect.get("source") != "prototype-field-status-effect"
        or effect.get("effectCount") != 1
        or effect_feedback.get("source") != "field-status-feedback"
        or effect_feedback.get("statusSource") != "prototype-field-status-effect"
        or effect_feedback.get("text") != "Ataho 독 -2 HP 34"
        or effect_feedback.get("fieldStatusSound") != "battleHit"
        or effect_feedback.get("fieldStatusSoundSrc") != "../extract_wlk/09.wav"
        or effect_feedback.get("fieldStatusSoundPlayed") is not True
        or poison.get("key") != "poison"
        or poison.get("name") != "독"
        or poison.get("characterName") != "Ataho"
        or poison.get("damage") != 2
        or poison.get("hpBefore") != 36
        or poison.get("hpAfter") != 34
        or poison.get("stepInterval") != 4
        or ((effect.get("progressEvent") or {}).get("kind")) != "field-status-effect-prototype"
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "field-status-effect-prototype"
        or auto_save.get("scope") != "field-status-effect"
        or auto_feedback.get("source") != "field-status-feedback"
        or auto_feedback.get("text") != "Ataho 독 -2 HP 34"
        or auto_feedback.get("fieldStatusSound") != "battleHit"
        or auto_feedback.get("fieldStatusSoundSrc") != "../extract_wlk/09.wav"
        or auto_feedback.get("fieldStatusSoundPlayed") is not True
        or auto_poison.get("key") != "poison"
        or auto_poison.get("damage") != 2
        or saved_payload.get("map") != "map1_02b"
        or saved_ataho.get("name") != "Ataho"
        or saved_ataho.get("hp") != 34
        or "poison" not in (saved_ataho.get("statuses") or [])
        or saved_ataho.get("fieldPoisonStepCount") != 0
        or counts.get("field-status-effect-prototype") != 1
        or objective_before.get("title") != "후보 필드 상태 완료 map1_02b"
        or objective_before.get("nextAction") != "완료 알림 확인"
        or "Ataho" not in str(objective_before.get("detail") or "")
        or "피해 2" not in str(objective_before.get("detail") or "")
        or state.get("objectiveActionResult") is not True
        or objective_action.get("action") != "field-status-completion-notice"
        or objective_action.get("handled") is not True
        or objective_action.get("activeId") != "field-status-complete:map1_02b"
        or completion_notice.get("blockId") != "field-status-complete:map1_02b"
        or "필드 상태 완료 1/1" not in completion_lines
        or "Ataho HP 36->34" not in completion_lines
        or "피해 2" not in completion_lines
        or completion_notice_feedback.get("source") != "field-status-feedback"
        or completion_notice_feedback.get("statusSource") != "field-status-completion-notice-feedback"
        or completion_notice_feedback.get("text") != "필드 상태 완료 1/1"
        or completion_notice_feedback.get("fieldStatusSound") != "menuConfirm"
        or completion_notice_feedback.get("fieldStatusSoundSrc") != "../extract_wlk/04.wav"
        or completion_notice_feedback.get("fieldStatusSoundPlayed") is not True
        or completion_notice_feedback.get("fieldStatusCompletionNoticeImplemented") is not True
        or "Ataho HP 34/36 독" not in play_hud
        or "상태 Ataho 독 -2 HP 34" not in play_hud
        or not feedback_log
        or not feedback_render_list
        or feedback.get("source") != "field-status-feedback"
        or feedback.get("statusSource") != "prototype-field-status-effect"
        or feedback.get("text") != "Ataho 독 -2 HP 34"
        or feedback.get("damage") != 2
        or feedback.get("hpBefore") != 36
        or feedback.get("hpAfter") != 34
        or feedback.get("durationMs") != 1100
        or feedback.get("fieldStatusSound") != "battleHit"
        or feedback.get("fieldStatusSoundSrc") != "../extract_wlk/09.wav"
        or feedback.get("fieldStatusSoundPlayed") is not True
        or feedback.get("browserFieldStatusFeedbackImplemented") is not True
        or feedback.get("prototypeFieldStatusEffectImplemented") is not True
        or feedback.get("prototypeFieldStatusMovementImplemented") is not False
        or feedback.get("originalStatusFormulaImplemented") is not False
        or feedback.get("originalStoryFlagRuntimeImplemented") is not False
        or feedback_render.get("source") != "field-status-feedback"
        or feedback_render.get("text") != "Ataho 독 -2 HP 34"
        or feedback_render.get("fieldStatusSound") != "battleHit"
        or feedback_render.get("fieldStatusSoundSrc") != "../extract_wlk/09.wav"
        or feedback_render.get("fieldStatusSoundPlayed") is not True
        or feedback_render.get("browserFieldStatusFeedbackImplemented") is not True
        or not notice_feedback_log
        or not notice_feedback_render_list
        or notice_feedback.get("source") != "field-status-feedback"
        or notice_feedback.get("statusSource") != "field-status-completion-notice-feedback"
        or notice_feedback.get("text") != "필드 상태 완료 1/1"
        or notice_feedback.get("fieldStatusSound") != "menuConfirm"
        or notice_feedback.get("fieldStatusSoundSrc") != "../extract_wlk/04.wav"
        or notice_feedback.get("fieldStatusSoundPlayed") is not True
        or notice_feedback.get("browserFieldStatusFeedbackImplemented") is not True
        or notice_feedback.get("prototypeFieldStatusEffectImplemented") is not True
        or notice_feedback.get("prototypeFieldStatusMovementImplemented") is not False
        or notice_feedback.get("fieldStatusCompletionNoticeImplemented") is not True
        or notice_feedback_render.get("source") != "field-status-feedback"
        or notice_feedback_render.get("statusSource") != "field-status-completion-notice-feedback"
        or notice_feedback_render.get("text") != "필드 상태 완료 1/1"
        or notice_feedback_render.get("fieldStatusSound") != "menuConfirm"
        or notice_feedback_render.get("fieldStatusSoundSrc") != "../extract_wlk/04.wav"
        or notice_feedback_render.get("fieldStatusSoundPlayed") is not True
        or notice_feedback_render.get("fieldStatusCompletionNoticeImplemented") is not True
        or state.get("originalStatusFormulaImplemented") is not False
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate movement field poison trace is incomplete: {state!r}")


def verify_field_poison_partial_continue(state: dict) -> None:
    saved_ataho = state.get("savedBeforeTitleAtaho") or {}
    restored_ataho = state.get("restoredAtaho") or {}
    step_auto_save = state.get("stepAutoSave") or {}
    step_result = state.get("stepResult") or {}
    step_counters = step_result.get("counters") or []
    step_counter = step_counters[0] if step_counters else {}
    step_feedback = step_result.get("statusFeedback") or {}
    step_auto_feedback = step_auto_save.get("statusFeedback") or {}
    feedback_log = state.get("fieldStatusFeedbackLog") or []
    feedback_render_list = state.get("fieldStatusFeedbackRender") or []
    feedback_last = state.get("fieldStatusFeedbackLast") or {}
    feedback_render = state.get("fieldStatusFeedbackLastRender") or {}
    feedback_log_text = " ".join(str((entry or {}).get("text") or "") for entry in feedback_log)
    saved_payload = state.get("savedBeforeTitle") or {}
    foot = state.get("foot") or {}
    title_labels = " ".join(str((item or {}).get("label") or "") for item in (state.get("titleItemsBefore") or []))
    play_hud = " ".join(str(line) for line in (state.get("playHudLines") or []))
    status_menu_lines = " ".join(str(line) for line in (state.get("statusMenuLines") or []))
    if (
        state.get("ok") is not True
        or state.get("moved") is not True
        or state.get("titleReturned") is not True
        or state.get("continueIndex", -1) < 0
        or "이어하기 map1_02b 11,12" not in title_labels
        or "상태 독" not in title_labels
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or foot.get("x") != 11
        or foot.get("y") != 12
        or state.get("quickLoadText") != "임시 불러오기"
        or saved_payload.get("map") != "map1_02b"
        or saved_ataho.get("name") != "Ataho"
        or saved_ataho.get("hp") != 36
        or "poison" not in (saved_ataho.get("statuses") or [])
        or saved_ataho.get("fieldPoisonStepCount") != 2
        or restored_ataho.get("name") != "Ataho"
        or restored_ataho.get("hp") != 36
        or "poison" not in (restored_ataho.get("statuses") or [])
        or restored_ataho.get("fieldPoisonStepCount") != 2
        or step_result.get("source") != "prototype-field-status-step"
        or step_counter.get("key") != "poison"
        or step_counter.get("stepCount") != 2
        or step_auto_save.get("saved") is not True
        or step_auto_save.get("source") != "field-status-step-prototype"
        or step_auto_save.get("scope") != "field-status-step"
        or step_feedback.get("source") != "field-status-feedback"
        or step_feedback.get("statusSource") != "prototype-field-status-step"
        or step_feedback.get("text") != "Ataho 독 2/4"
        or step_feedback.get("stepCount") != 2
        or step_feedback.get("stepInterval") != 4
        or step_feedback.get("browserFieldStatusFeedbackImplemented") is not True
        or step_feedback.get("prototypeFieldStatusStepImplemented") is not True
        or step_feedback.get("prototypeFieldStatusEffectImplemented") is not False
        or step_feedback.get("prototypeFieldStatusMovementImplemented") is not False
        or step_feedback.get("prototypeFieldStatusMovementStepImplemented") is not False
        or step_auto_feedback.get("source") != "field-status-feedback"
        or step_auto_feedback.get("text") != "Ataho 독 2/4"
        or "Ataho 독 1/4" not in feedback_log_text
        or "Ataho 독 2/4" not in feedback_log_text
        or not feedback_render_list
        or feedback_last.get("source") != "field-status-feedback"
        or feedback_last.get("text") != "Ataho 독 2/4"
        or feedback_last.get("durationMs") != 1100
        or feedback_last.get("browserFieldStatusFeedbackImplemented") is not True
        or feedback_render.get("source") != "field-status-feedback"
        or feedback_render.get("text") != "Ataho 독 2/4"
        or feedback_render.get("browserFieldStatusFeedbackImplemented") is not True
        or "Ataho HP 36/36 독" not in play_hud
        or "필드 독 2/4" not in status_menu_lines
        or state.get("originalStatusFormulaImplemented") is not False
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate movement field poison partial continue is incomplete: {state!r}")


def verify_field_poison_cure_continue(state: dict) -> None:
    saved_ataho = state.get("savedBeforeTitleAtaho") or {}
    restored_ataho = state.get("restoredAtaho") or {}
    saved_antidote = state.get("savedBeforeTitleAntidote") or {}
    restored_antidote = state.get("restoredAntidote") or {}
    item_use = state.get("itemUse") or {}
    item_auto_save = state.get("itemAutoSave") or {}
    stale_feedback_before = state.get("staleFeedbackBeforeUse") or {}
    foot = state.get("foot") or {}
    title_labels = " ".join(str((item or {}).get("label") or "") for item in (state.get("titleItemsBefore") or []))
    play_hud = " ".join(str(line) for line in (state.get("playHudLines") or []))
    status_menu_lines = " ".join(str(line) for line in (state.get("statusMenuLines") or []))
    if (
        state.get("ok") is not True
        or state.get("used") is not True
        or state.get("titleReturned") is not True
        or state.get("continueIndex", -1) < 0
        or "이어하기 map1_02b 11,12" not in title_labels
        or "상태 독" in title_labels
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or foot.get("x") != 11
        or foot.get("y") != 12
        or state.get("quickLoadText") != "임시 불러오기"
        or "해독초 Ataho 독 회복" not in str(state.get("itemNotice") or "")
        or item_use.get("itemKey") != "item_2"
        or item_use.get("itemName") != "해독초"
        or item_use.get("targetName") != "Ataho"
        or item_use.get("countBefore") != 1
        or item_use.get("countAfter") != 0
        or "poison" not in (item_use.get("statusesBefore") or [])
        or item_use.get("statusesAfter") != []
        or item_use.get("fieldPoisonStepCountBefore") != 2
        or item_use.get("fieldPoisonStepCountAfter") != 0
        or item_auto_save.get("saved") is not True
        or item_auto_save.get("source") != "item-use-prototype"
        or item_auto_save.get("fieldPoisonStepCountBefore") != 2
        or item_auto_save.get("fieldPoisonStepCountAfter") != 0
        or stale_feedback_before.get("source") != "field-status-feedback"
        or stale_feedback_before.get("text") != "Ataho 독 -2 HP 34"
        or state.get("staleFeedbackCountBeforeUse") != 1
        or state.get("staleFeedbackCountAfterUse") != 0
        or state.get("staleFeedbackAfterUse") is not None
        or state.get("staleFeedbackRenderAfterUse") is not None
        or saved_ataho.get("name") != "Ataho"
        or saved_ataho.get("hp") != 36
        or saved_ataho.get("statuses") != []
        or saved_ataho.get("fieldPoisonStepCount") != 0
        or saved_antidote.get("count") != 0
        or restored_ataho.get("name") != "Ataho"
        or restored_ataho.get("hp") != 36
        or restored_ataho.get("statuses") != []
        or restored_ataho.get("fieldPoisonStepCount") != 0
        or restored_antidote.get("count") != 0
        or "독" in play_hud
        or "필드 독" in status_menu_lines
        or state.get("originalStatusFormulaImplemented") is not False
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate movement field poison cure continue is incomplete: {state!r}")


def verify_field_paralysis_cure_continue(state: dict) -> None:
    saved_ataho = state.get("savedBeforeTitleAtaho") or {}
    restored_ataho = state.get("restoredAtaho") or {}
    saved_antidote = state.get("savedBeforeTitleAntidote") or {}
    restored_antidote = state.get("restoredAntidote") or {}
    item_use = state.get("itemUse") or {}
    item_auto_save = state.get("itemAutoSave") or {}
    stale_feedback_before = state.get("staleFeedbackBeforeUse") or {}
    foot = state.get("foot") or {}
    title_labels = " ".join(str((item or {}).get("label") or "") for item in (state.get("titleItemsBefore") or []))
    play_hud = " ".join(str(line) for line in (state.get("playHudLines") or []))
    status_menu_lines = " ".join(str(line) for line in (state.get("statusMenuLines") or []))
    if (
        state.get("ok") is not True
        or state.get("used") is not True
        or state.get("titleReturned") is not True
        or state.get("continueIndex", -1) < 0
        or "이어하기 map1_02b 11,12" not in title_labels
        or "상태 마비" in title_labels
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or foot.get("x") != 11
        or foot.get("y") != 12
        or state.get("quickLoadText") != "임시 불러오기"
        or "해독초 Ataho 마비 회복" not in str(state.get("itemNotice") or "")
        or item_use.get("itemKey") != "item_2"
        or item_use.get("itemName") != "해독초"
        or item_use.get("targetName") != "Ataho"
        or item_use.get("countBefore") != 1
        or item_use.get("countAfter") != 0
        or "paralysis" not in (item_use.get("statusesBefore") or [])
        or item_use.get("statusesAfter") != []
        or item_use.get("fieldParalysisMoveCountBefore") != 2
        or item_use.get("fieldParalysisMoveCountAfter") != 0
        or item_auto_save.get("saved") is not True
        or item_auto_save.get("source") != "item-use-prototype"
        or item_auto_save.get("fieldParalysisMoveCountBefore") != 2
        or item_auto_save.get("fieldParalysisMoveCountAfter") != 0
        or state.get("staleBlockAfterUse") is not None
        or state.get("staleStepAfterUse") is not None
        or stale_feedback_before.get("source") != "field-status-feedback"
        or stale_feedback_before.get("text") != "Ataho 마비 이동 실패"
        or state.get("staleFeedbackCountBeforeUse") != 1
        or state.get("staleFeedbackCountAfterUse") != 0
        or state.get("staleFeedbackAfterUse") is not None
        or state.get("staleFeedbackRenderAfterUse") is not None
        or saved_ataho.get("name") != "Ataho"
        or saved_ataho.get("hp") != 36
        or saved_ataho.get("statuses") != []
        or saved_ataho.get("fieldParalysisMoveCount") != 0
        or saved_antidote.get("count") != 0
        or restored_ataho.get("name") != "Ataho"
        or restored_ataho.get("hp") != 36
        or restored_ataho.get("statuses") != []
        or restored_ataho.get("fieldParalysisMoveCount") != 0
        or restored_antidote.get("count") != 0
        or "마비" in play_hud
        or "필드 마비" in status_menu_lines
        or state.get("originalStatusFormulaImplemented") is not False
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate movement field paralysis cure continue is incomplete: {state!r}")


def verify_field_poison_effect_continue(state: dict) -> None:
    effect = state.get("effectBeforeTitle") or {}
    effects = effect.get("effects") or []
    poison = effects[0] if effects else {}
    auto_save = state.get("fieldStatusAutoSave") or {}
    auto_effects = auto_save.get("effects") or []
    auto_poison = auto_effects[0] if auto_effects else {}
    saved_payload = state.get("savedBeforeTitle") or {}
    saved_ataho = state.get("savedBeforeTitleAtaho") or {}
    restored_ataho = state.get("restoredAtaho") or {}
    foot = state.get("foot") or {}
    objective_before_title = state.get("objectiveBeforeTitle") or {}
    objective_after_continue = state.get("objectiveAfterContinue") or {}
    objective_action = state.get("objectiveAction") or {}
    completion_notice = state.get("completionNotice") or {}
    notice_feedback = state.get("fieldStatusNoticeFeedbackLast") or {}
    notice_feedback_render = state.get("fieldStatusNoticeFeedbackLastRender") or {}
    completion_notice_feedback = completion_notice.get("feedback") or {}
    auto_feedback = auto_save.get("statusFeedback") or {}
    title_labels = " ".join(str((item or {}).get("label") or "") for item in (state.get("titleItemsBefore") or []))
    play_hud = " ".join(str(line) for line in (state.get("playHudLines") or []))
    completion_lines = " ".join(str(line) for line in (completion_notice.get("lines") or []))
    if (
        state.get("ok") is not True
        or state.get("moved") is not True
        or state.get("titleReturned") is not True
        or state.get("continueIndex", -1) < 0
        or "이어하기 map1_02b 11,12" not in title_labels
        or "상태 독" not in title_labels
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or foot.get("x") != 11
        or foot.get("y") != 12
        or state.get("quickLoadText") != "임시 불러오기"
        or saved_payload.get("map") != "map1_02b"
        or saved_ataho.get("name") != "Ataho"
        or saved_ataho.get("hp") != 34
        or "poison" not in (saved_ataho.get("statuses") or [])
        or saved_ataho.get("fieldPoisonStepCount") != 0
        or restored_ataho.get("name") != "Ataho"
        or restored_ataho.get("hp") != 34
        or "poison" not in (restored_ataho.get("statuses") or [])
        or restored_ataho.get("fieldPoisonStepCount") != 0
        or effect.get("source") != "prototype-field-status-effect"
        or effect.get("effectCount") != 1
        or poison.get("key") != "poison"
        or poison.get("characterName") != "Ataho"
        or poison.get("damage") != 2
        or poison.get("hpBefore") != 36
        or poison.get("hpAfter") != 34
        or poison.get("stepInterval") != 4
        or ((effect.get("progressEvent") or {}).get("kind")) != "field-status-effect-prototype"
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "field-status-effect-prototype"
        or auto_save.get("scope") != "field-status-effect"
        or auto_poison.get("key") != "poison"
        or auto_poison.get("damage") != 2
        or objective_before_title.get("title") != "후보 필드 상태 완료 map1_02b"
        or objective_before_title.get("nextAction") != "완료 알림 확인"
        or "피해 2" not in str(objective_before_title.get("detail") or "")
        or objective_after_continue.get("title") != "후보 필드 상태 완료 map1_02b"
        or objective_after_continue.get("nextAction") != "완료 알림 확인"
        or "피해 2" not in str(objective_after_continue.get("detail") or "")
        or state.get("objectiveActionResult") is not True
        or objective_action.get("action") != "field-status-completion-notice"
        or objective_action.get("handled") is not True
        or objective_action.get("activeId") != "field-status-complete:map1_02b"
        or completion_notice.get("blockId") != "field-status-complete:map1_02b"
        or "필드 상태 완료 1/1" not in completion_lines
        or "Ataho HP 36->34" not in completion_lines
        or "피해 2" not in completion_lines
        or completion_notice_feedback.get("statusSource") != "field-status-completion-notice-feedback"
        or completion_notice_feedback.get("text") != "필드 상태 완료 1/1"
        or completion_notice_feedback.get("fieldStatusSound") != "menuConfirm"
        or completion_notice_feedback.get("fieldStatusSoundSrc") != "../extract_wlk/04.wav"
        or completion_notice_feedback.get("fieldStatusSoundPlayed") is not True
        or completion_notice_feedback.get("fieldStatusCompletionNoticeImplemented") is not True
        or notice_feedback.get("statusSource") != "field-status-completion-notice-feedback"
        or notice_feedback.get("text") != "필드 상태 완료 1/1"
        or notice_feedback.get("fieldStatusSound") != "menuConfirm"
        or notice_feedback.get("fieldStatusSoundSrc") != "../extract_wlk/04.wav"
        or notice_feedback.get("fieldStatusSoundPlayed") is not True
        or notice_feedback.get("fieldStatusCompletionNoticeImplemented") is not True
        or notice_feedback_render.get("statusSource") != "field-status-completion-notice-feedback"
        or notice_feedback_render.get("text") != "필드 상태 완료 1/1"
        or notice_feedback_render.get("fieldStatusSound") != "menuConfirm"
        or notice_feedback_render.get("fieldStatusSoundSrc") != "../extract_wlk/04.wav"
        or notice_feedback_render.get("fieldStatusSoundPlayed") is not True
        or "Ataho HP 34/36 독" not in play_hud
        or state.get("originalStatusFormulaImplemented") is not False
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate movement field poison effect title continue is incomplete: {state!r}")


def verify_field_paralysis_movement(state: dict) -> None:
    attempts = state.get("attempts") or []
    attempt1 = attempts[0] if len(attempts) > 0 else {}
    attempt2 = attempts[1] if len(attempts) > 1 else {}
    attempt3 = attempts[2] if len(attempts) > 2 else {}
    attempt1_status = attempt1.get("movementStatus") or {}
    attempt2_status = attempt2.get("movementStatus") or {}
    block = state.get("movementBlock") or {}
    blocks = block.get("blocks") or []
    first_block = blocks[0] if blocks else {}
    counters = block.get("counters") or []
    first_counter = counters[0] if counters else {}
    auto_save = state.get("movementBlockAutoSave") or {}
    auto_blocks = auto_save.get("blocks") or []
    auto_block = auto_blocks[0] if auto_blocks else {}
    saved_ataho = state.get("savedBeforeTitleAtaho") or {}
    restored_ataho = state.get("restoredAtaho") or {}
    foot = state.get("foot") or {}
    objective_before_title = state.get("objectiveBeforeTitle") or {}
    objective_after_continue = state.get("objectiveAfterContinue") or {}
    objective_action = state.get("objectiveAction") or {}
    completion_notice = state.get("completionNotice") or {}
    notice_feedback = state.get("fieldStatusNoticeFeedbackLast") or {}
    notice_feedback_render = state.get("fieldStatusNoticeFeedbackLastRender") or {}
    completion_notice_feedback = completion_notice.get("feedback") or {}
    feedback = state.get("movementFeedbackLast") or {}
    feedback_render = state.get("movementFeedbackLastRender") or {}
    feedback_log = state.get("movementFeedbackLog") or []
    feedback_render_list = state.get("movementFeedbackRender") or []
    block_feedback = block.get("statusFeedback") or {}
    auto_feedback = auto_save.get("statusFeedback") or {}
    title_labels = " ".join(str((item or {}).get("label") or "") for item in (state.get("titleItemsBefore") or []))
    play_hud = " ".join(str(line) for line in (state.get("playHudLines") or []))
    status_menu_lines = " ".join(str(line) for line in (state.get("statusMenuLines") or []))
    completion_lines = " ".join(str(line) for line in (completion_notice.get("lines") or []))
    if (
        state.get("ok") is not True
        or len(attempts) != 3
        or attempt1.get("moved") is not True
        or attempt1.get("blocked") is not False
        or attempt1.get("beforeCounter") != 0
        or attempt1.get("afterCounter") != 1
        or attempt1_status.get("source") != "prototype-field-status-movement-step"
        or attempt2.get("moved") is not True
        or attempt2.get("blocked") is not False
        or attempt2.get("beforeCounter") != 1
        or attempt2.get("afterCounter") != 2
        or attempt2_status.get("source") != "prototype-field-status-movement-step"
        or attempt3.get("moved") is not False
        or attempt3.get("blocked") is not True
        or attempt3.get("beforeCounter") != 2
        or attempt3.get("afterCounter") != 0
        or (attempt3.get("beforeFoot") or {}).get("x") != 11
        or (attempt3.get("beforeFoot") or {}).get("y") != 12
        or (attempt3.get("afterFoot") or {}).get("x") != 11
        or (attempt3.get("afterFoot") or {}).get("y") != 12
        or block.get("source") != "prototype-field-status-movement-block"
        or block.get("blocked") is not True
        or block.get("blockCount") != 1
        or block_feedback.get("source") != "field-status-feedback"
        or block_feedback.get("statusSource") != "prototype-field-status-movement-block"
        or block_feedback.get("text") != "Ataho 마비 이동 실패"
        or block_feedback.get("fieldStatusSound") != "menuMove"
        or block_feedback.get("fieldStatusSoundSrc") != "../extract_wlk/03.wav"
        or block_feedback.get("fieldStatusSoundPlayed") is not True
        or first_counter.get("key") != "paralysis"
        or first_counter.get("blocked") is not True
        or first_counter.get("moveCount") != 0
        or first_counter.get("moveInterval") != 3
        or first_block.get("key") != "paralysis"
        or first_block.get("name") != "마비"
        or first_block.get("characterName") != "Ataho"
        or first_block.get("moveInterval") != 3
        or (first_block.get("fromTile") or {}).get("x") != 11
        or (first_block.get("fromTile") or {}).get("y") != 12
        or (first_block.get("targetTile") or {}).get("x") != 12
        or (first_block.get("targetTile") or {}).get("y") != 12
        or ((block.get("progressEvent") or {}).get("kind")) != "field-status-movement-block-prototype"
        or auto_save.get("saved") is not True
        or auto_save.get("source") != "field-status-movement-block-prototype"
        or auto_save.get("scope") != "field-status-movement-block"
        or auto_feedback.get("source") != "field-status-feedback"
        or auto_feedback.get("text") != "Ataho 마비 이동 실패"
        or auto_feedback.get("fieldStatusSound") != "menuMove"
        or auto_feedback.get("fieldStatusSoundSrc") != "../extract_wlk/03.wav"
        or auto_feedback.get("fieldStatusSoundPlayed") is not True
        or auto_block.get("key") != "paralysis"
        or saved_ataho.get("name") != "Ataho"
        or saved_ataho.get("hp") != 36
        or "paralysis" not in (saved_ataho.get("statuses") or [])
        or saved_ataho.get("fieldParalysisMoveCount") != 0
        or state.get("titleReturned") is not True
        or state.get("continueIndex", -1) < 0
        or "이어하기 map1_02b 11,12" not in title_labels
        or "상태 마비" not in title_labels
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or foot.get("x") != 11
        or foot.get("y") != 12
        or state.get("quickLoadText") != "임시 불러오기"
        or restored_ataho.get("name") != "Ataho"
        or restored_ataho.get("hp") != 36
        or "paralysis" not in (restored_ataho.get("statuses") or [])
        or restored_ataho.get("fieldParalysisMoveCount") != 0
        or objective_before_title.get("title") != "후보 필드 상태 완료 map1_02b"
        or objective_before_title.get("nextAction") != "완료 알림 확인"
        or "이동 실패" not in str(objective_before_title.get("detail") or "")
        or objective_after_continue.get("title") != "후보 필드 상태 완료 map1_02b"
        or objective_after_continue.get("nextAction") != "완료 알림 확인"
        or "이동 실패" not in str(objective_after_continue.get("detail") or "")
        or state.get("objectiveActionResult") is not True
        or objective_action.get("action") != "field-status-completion-notice"
        or objective_action.get("handled") is not True
        or objective_action.get("activeId") != "field-status-complete:map1_02b"
        or completion_notice.get("blockId") != "field-status-complete:map1_02b"
        or "필드 상태 완료 1/1" not in completion_lines
        or "Ataho 마비" not in completion_lines
        or "이동 실패" not in completion_lines
        or "이동 3회마다 차단" not in completion_lines
        or completion_notice_feedback.get("source") != "field-status-feedback"
        or completion_notice_feedback.get("statusSource") != "field-status-completion-notice-feedback"
        or completion_notice_feedback.get("text") != "필드 상태 완료 1/1"
        or completion_notice_feedback.get("fieldStatusSound") != "menuConfirm"
        or completion_notice_feedback.get("fieldStatusSoundSrc") != "../extract_wlk/04.wav"
        or completion_notice_feedback.get("fieldStatusSoundPlayed") is not True
        or completion_notice_feedback.get("fieldStatusCompletionNoticeImplemented") is not True
        or "Ataho HP 36/36 마비" not in play_hud
        or "상태 Ataho 마비 이동 실패" not in play_hud
        or "상태 마비" not in status_menu_lines
        or not feedback_log
        or not feedback_render_list
        or feedback.get("source") != "field-status-feedback"
        or feedback.get("statusSource") != "prototype-field-status-movement-block"
        or feedback.get("text") != "Ataho 마비 이동 실패"
        or feedback.get("blocked") is not True
        or feedback.get("durationMs") != 1100
        or feedback.get("fieldStatusSound") != "menuMove"
        or feedback.get("fieldStatusSoundSrc") != "../extract_wlk/03.wav"
        or feedback.get("fieldStatusSoundPlayed") is not True
        or feedback.get("browserFieldStatusFeedbackImplemented") is not True
        or feedback.get("prototypeFieldStatusEffectImplemented") is not False
        or feedback.get("prototypeFieldStatusMovementImplemented") is not True
        or feedback.get("originalStatusFormulaImplemented") is not False
        or feedback.get("originalStoryFlagRuntimeImplemented") is not False
        or feedback_render.get("source") != "field-status-feedback"
        or feedback_render.get("text") != "Ataho 마비 이동 실패"
        or feedback_render.get("fieldStatusSound") != "menuMove"
        or feedback_render.get("fieldStatusSoundSrc") != "../extract_wlk/03.wav"
        or feedback_render.get("fieldStatusSoundPlayed") is not True
        or feedback_render.get("browserFieldStatusFeedbackImplemented") is not True
        or notice_feedback.get("source") != "field-status-feedback"
        or notice_feedback.get("statusSource") != "field-status-completion-notice-feedback"
        or notice_feedback.get("text") != "필드 상태 완료 1/1"
        or notice_feedback.get("fieldStatusSound") != "menuConfirm"
        or notice_feedback.get("fieldStatusSoundSrc") != "../extract_wlk/04.wav"
        or notice_feedback.get("fieldStatusSoundPlayed") is not True
        or notice_feedback.get("browserFieldStatusFeedbackImplemented") is not True
        or notice_feedback.get("prototypeFieldStatusEffectImplemented") is not True
        or notice_feedback.get("prototypeFieldStatusMovementImplemented") is not True
        or notice_feedback.get("fieldStatusCompletionNoticeImplemented") is not True
        or notice_feedback_render.get("source") != "field-status-feedback"
        or notice_feedback_render.get("statusSource") != "field-status-completion-notice-feedback"
        or notice_feedback_render.get("text") != "필드 상태 완료 1/1"
        or notice_feedback_render.get("fieldStatusSound") != "menuConfirm"
        or notice_feedback_render.get("fieldStatusSoundSrc") != "../extract_wlk/04.wav"
        or notice_feedback_render.get("fieldStatusSoundPlayed") is not True
        or notice_feedback_render.get("fieldStatusCompletionNoticeImplemented") is not True
        or state.get("originalStatusFormulaImplemented") is not False
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate movement field paralysis movement is incomplete: {state!r}")


def verify_field_paralysis_partial_continue(state: dict) -> None:
    saved_ataho = state.get("savedBeforeTitleAtaho") or {}
    restored_ataho = state.get("restoredAtaho") or {}
    movement_step = state.get("movementStep") or {}
    movement_step_auto = state.get("movementStepAutoSave") or {}
    counters = movement_step.get("counters") or []
    counter = counters[0] if counters else {}
    step_feedback = movement_step.get("statusFeedback") or {}
    step_auto_feedback = movement_step_auto.get("statusFeedback") or {}
    feedback_log = state.get("movementFeedbackLog") or []
    feedback_render_list = state.get("movementFeedbackRender") or []
    feedback_last = state.get("movementFeedbackLast") or {}
    feedback_render = state.get("movementFeedbackLastRender") or {}
    feedback_log_text = " ".join(str((entry or {}).get("text") or "") for entry in feedback_log)
    foot = state.get("foot") or {}
    title_labels = " ".join(str((item or {}).get("label") or "") for item in (state.get("titleItemsBefore") or []))
    play_hud = " ".join(str(line) for line in (state.get("playHudLines") or []))
    status_menu_lines = " ".join(str(line) for line in (state.get("statusMenuLines") or []))
    if (
        state.get("ok") is not True
        or state.get("moved") is not True
        or state.get("titleReturned") is not True
        or state.get("continueIndex", -1) < 0
        or "이어하기 map1_02b 11,12" not in title_labels
        or "상태 마비" not in title_labels
        or state.get("scene") != "map"
        or state.get("map") != "map1_02b"
        or foot.get("x") != 11
        or foot.get("y") != 12
        or state.get("quickLoadText") != "임시 불러오기"
        or saved_ataho.get("name") != "Ataho"
        or saved_ataho.get("hp") != 36
        or "paralysis" not in (saved_ataho.get("statuses") or [])
        or saved_ataho.get("fieldParalysisMoveCount") != 2
        or restored_ataho.get("name") != "Ataho"
        or restored_ataho.get("hp") != 36
        or "paralysis" not in (restored_ataho.get("statuses") or [])
        or restored_ataho.get("fieldParalysisMoveCount") != 2
        or movement_step.get("source") != "prototype-field-status-movement-step"
        or movement_step.get("blocked") is not False
        or counter.get("key") != "paralysis"
        or counter.get("moveCount") != 2
        or counter.get("moveInterval") != 3
        or movement_step_auto.get("saved") is not True
        or movement_step_auto.get("source") != "field-status-movement-step-prototype"
        or movement_step_auto.get("scope") != "field-status-movement-step"
        or step_feedback.get("source") != "field-status-feedback"
        or step_feedback.get("statusSource") != "prototype-field-status-movement-step"
        or step_feedback.get("text") != "Ataho 마비 2/3"
        or step_feedback.get("moveCount") != 2
        or step_feedback.get("moveInterval") != 3
        or step_feedback.get("browserFieldStatusFeedbackImplemented") is not True
        or step_feedback.get("prototypeFieldStatusEffectImplemented") is not False
        or step_feedback.get("prototypeFieldStatusMovementImplemented") is not True
        or step_feedback.get("prototypeFieldStatusMovementStepImplemented") is not True
        or step_auto_feedback.get("source") != "field-status-feedback"
        or step_auto_feedback.get("text") != "Ataho 마비 2/3"
        or "Ataho 마비 1/3" not in feedback_log_text
        or "Ataho 마비 2/3" not in feedback_log_text
        or not feedback_render_list
        or feedback_last.get("source") != "field-status-feedback"
        or feedback_last.get("text") != "Ataho 마비 2/3"
        or feedback_last.get("durationMs") != 1100
        or feedback_last.get("browserFieldStatusFeedbackImplemented") is not True
        or feedback_render.get("source") != "field-status-feedback"
        or feedback_render.get("text") != "Ataho 마비 2/3"
        or feedback_render.get("browserFieldStatusFeedbackImplemented") is not True
        or "Ataho HP 36/36 마비" not in play_hud
        or "필드 마비 2/3" not in status_menu_lines
        or state.get("originalStatusFormulaImplemented") is not False
        or state.get("originalStoryFlagRuntimeImplemented") is not False
    ):
        raise WebDriverError(f"candidate movement field paralysis partial continue is incomplete: {state!r}")


def write_report(report: dict) -> None:
    out_dir = ROOT / "out"
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "candidate_movement_browser_smoke.json").write_text(
        json.dumps(report, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    lines = [
        "# Candidate Movement Browser Smoke",
        "",
        f"- status: `{report.get('status')}`",
        f"- trace: `{report.get('trace')}`",
        f"- movement step sound: `{report.get('movementStepSound')}`",
        f"- movement bump: `{report.get('movementBump')}`",
        f"- wall-slide facing: `{report.get('wallSlideFacing')}`",
        f"- field poison: `{report.get('fieldPoison')}`",
        f"- field poison partial continue: `{report.get('fieldPoisonPartialContinue')}`",
        f"- field poison cure continue: `{report.get('fieldPoisonCureContinue')}`",
        f"- field paralysis cure continue: `{report.get('fieldParalysisCureContinue')}`",
        f"- field poison effect continue: `{report.get('fieldPoisonEffectContinue')}`",
        f"- field paralysis movement: `{report.get('fieldParalysisMovement')}`",
        f"- field paralysis partial continue: `{report.get('fieldParalysisPartialContinue')}`",
        f"- checksum: `{report.get('checksum')}`",
        "",
    ]


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_movement_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,
            )
            url = load_map(base, port, session_id, {"map": "map1_02b", "startTile": "11,12"})
            trace = execute_js(port, session_id, movement_trace_script(), timeout=5)
            verify_trace(trace)
            movement_step_sound = execute_js(port, session_id, movement_step_sound_script(), timeout=5)
            verify_movement_step_sound(movement_step_sound)
            blocked_movement_bump = execute_js(port, session_id, blocked_movement_bump_script(), timeout=5)
            verify_blocked_movement_bump(blocked_movement_bump)
            wall_slide_direction = execute_js(port, session_id, wall_slide_direction_script(), timeout=5)
            verify_wall_slide_direction(wall_slide_direction)
            party_trail_animation = execute_js(port, session_id, party_trail_animation_script(), timeout=8)
            verify_party_trail_animation(party_trail_animation)
            field_poison = execute_js(port, session_id, field_poison_trace_script(), timeout=8)
            verify_field_poison_trace(field_poison)
            execute_js(port, session_id, field_poison_partial_continue_script(), timeout=8)
            field_poison_partial_continue = wait_for_field_poison_partial_continue(port, session_id)
            verify_field_poison_partial_continue(field_poison_partial_continue)
            execute_js(port, session_id, field_poison_cure_continue_script(), timeout=8)
            field_poison_cure_continue = wait_for_field_poison_cure_continue(port, session_id)
            verify_field_poison_cure_continue(field_poison_cure_continue)
            execute_js(port, session_id, field_paralysis_cure_continue_script(), timeout=8)
            field_paralysis_cure_continue = wait_for_field_paralysis_cure_continue(port, session_id)
            verify_field_paralysis_cure_continue(field_paralysis_cure_continue)
            execute_js(port, session_id, field_poison_effect_continue_script(), timeout=8)
            field_poison_effect_continue = wait_for_field_poison_effect_continue(port, session_id)
            verify_field_poison_effect_continue(field_poison_effect_continue)
            execute_js(port, session_id, field_paralysis_movement_script(), timeout=8)
            field_paralysis_movement = wait_for_field_paralysis_movement(port, session_id)
            verify_field_paralysis_movement(field_paralysis_movement)
            execute_js(port, session_id, field_paralysis_partial_continue_script(), timeout=8)
            field_paralysis_partial_continue = wait_for_field_paralysis_partial_continue(port, session_id)
            verify_field_paralysis_partial_continue(field_paralysis_partial_continue)
            checksum = int(execute_js(port, session_id, canvas_checksum_script(), timeout=3))
            if checksum == 0:
                raise WebDriverError("candidate movement trace rendered a blank canvas")
            samples = trace.get("samples") or []
            sample_summary = ",".join(
                f"{row.get('label')}:{int(row.get('x'))},{int(row.get('y'))}:f{row.get('frame')}:p{row.get('walkScriptPhase')}:foot{row.get('foot', {}).get('x')},{row.get('foot', {}).get('y')}"
                for row in samples
            )
            selector_summary = ",".join(
                f"{row.get('label')}:{row.get('selector')}"
                for row in samples
            )
            source_summary = ",".join(
                f"{row.get('label')}:{(row.get('sourceRect') or {}).get('x')},{(row.get('sourceRect') or {}).get('y')},"
                f"{(row.get('sourceRect') or {}).get('width')}x{(row.get('sourceRect') or {}).get('height')}"
                for row in samples
            )
            projection_summary = ",".join(
                f"{row.get('label')}:{((row.get('projection') or {}).get('projectedScreen') or {}).get('x')},"
                f"{((row.get('projection') or {}).get('projectedScreen') or {}).get('y')}->"
                f"{((row.get('projection') or {}).get('webProjectedSpriteTop') or {}).get('x')},"
                f"{((row.get('projection') or {}).get('webProjectedSpriteTop') or {}).get('y')}:"
                f"fp{((row.get('projection') or {}).get('fixedPoint') or {}).get('x')},"
                f"{((row.get('projection') or {}).get('fixedPoint') or {}).get('y')}"
                for row in samples
            )
            start_x = int(samples[0].get("x")) if samples else 0
            start_y = int(samples[0].get("y")) if samples else 0
            tile_size = 16
            movement_delta_summary = ",".join(
                f"{row.get('label')}:{int(row.get('x')) - start_x},{int(row.get('y')) - start_y}"
                for row in samples
            )
            step_fraction_summary = ",".join(
                f"{row.get('label')}:{max(0, min(1, (int(row.get('x')) - start_x) / tile_size)):.2f}"
                for row in samples
            )
            interpolation_offset_summary = ",".join(
                f"{row.get('label')}:{(((row.get('projection') or {}).get('interpolationOffset') or {}).get('x'))},"
                f"{(((row.get('projection') or {}).get('interpolationOffset') or {}).get('y'))}"
                for row in samples
            )
            report = {
                "status": "passed",
                "base": base,
                "url": url,
                "trace": (
                    f"map={trace.get('map')} "
                    f"frameMs={trace.get('originalFrameMs')} "
                    f"tileStepMs={trace.get('defaultTileStepMs')} "
                    f"commands={trace.get('playerOriginalTileStepCommands')} "
                    f"cadence={trace.get('playerOriginalWalkCadenceCommands')} "
                    f"walkFrames={trace.get('playerWalkFrames')} "
                    f"start={trace.get('startTile', {}).get('x')},{trace.get('startTile', {}).get('y')} "
                    f"target={trace.get('targetTile', {}).get('x')},{trace.get('targetTile', {}).get('y')} "
                    f"selectors={selector_summary} "
                    f"sources={source_summary} "
                    f"sourceMatches={all((row or {}).get('sourceMatchesSelector') is True for row in samples)} "
                    f"projectionBias={trace.get('originalProjectionBias', {}).get('x')},{trace.get('originalProjectionBias', {}).get('y')} "
                    f"fixedShift={trace.get('originalProjectionFixedShift')} "
                    f"projectionMatches={all((row or {}).get('projectionMatchesDraw') is True for row in samples)} "
                    f"projections={projection_summary} "
                    f"movementDeltas={movement_delta_summary} "
                    f"stepFractions={step_fraction_summary} "
                    f"interpolationOffsets={interpolation_offset_summary} "
                    f"samples={sample_summary}"
                ),
                "movementStepSound": (
                    f"map={movement_step_sound.get('map')} "
                    f"input={(movement_step_sound.get('fieldMovementSound') or {}).get('inputSource')} "
                    f"from={(movement_step_sound.get('beforeFoot') or {}).get('x')},{(movement_step_sound.get('beforeFoot') or {}).get('y')} "
                    f"target={((movement_step_sound.get('fieldMovementSound') or {}).get('targetTile') or {}).get('x')},"
                    f"{((movement_step_sound.get('fieldMovementSound') or {}).get('targetTile') or {}).get('y')} "
                    f"after={(movement_step_sound.get('afterFoot') or {}).get('x')},{(movement_step_sound.get('afterFoot') or {}).get('y')} "
                    f"stepActiveAfterInput={movement_step_sound.get('stepActiveAfterInput')} "
                    f"stepActiveAfterComplete={movement_step_sound.get('stepActiveAfterComplete')} "
                    f"fieldMovementSound={(movement_step_sound.get('fieldMovementSound') or {}).get('source')}:"
                    f"{(movement_step_sound.get('fieldMovementSound') or {}).get('soundKey')} "
                    f"fieldMovementSoundSrc={(movement_step_sound.get('fieldMovementSound') or {}).get('soundSrc')} "
                    f"fieldMovementSoundPlayed={(movement_step_sound.get('fieldMovementSound') or {}).get('soundPlayed')} "
                    f"stepSoundCount={(movement_step_sound.get('soundCounts') or {}).get('step')} "
                    f"browserFieldMovementSound={(movement_step_sound.get('fieldMovementSound') or {}).get('browserFieldMovementSoundImplemented')}"
                ),
                "movementBump": (
                    f"map={blocked_movement_bump.get('map')} "
                    f"source={(blocked_movement_bump.get('effect') or {}).get('source')} "
                    f"reason={(blocked_movement_bump.get('effect') or {}).get('reason')} "
                    f"from={(blocked_movement_bump.get('beforeFoot') or {}).get('x')},{(blocked_movement_bump.get('beforeFoot') or {}).get('y')} "
                    f"target={((blocked_movement_bump.get('effect') or {}).get('targetTile') or {}).get('x')},{((blocked_movement_bump.get('effect') or {}).get('targetTile') or {}).get('y')} "
                    f"direction={((blocked_movement_bump.get('attempt') or {}).get('direction') or {}).get('name')} "
                    f"playerStayed={blocked_movement_bump.get('beforeFoot') == blocked_movement_bump.get('afterFoot')} "
                    f"stepActive={blocked_movement_bump.get('playerStepActive')} "
                    f"duration={blocked_movement_bump.get('movementBumpDurationMs')} "
                    f"log={blocked_movement_bump.get('logLength')} "
                    f"render={bool(blocked_movement_bump.get('render'))} "
                    f"browserBump={(blocked_movement_bump.get('effect') or {}).get('browserMovementBumpFeedbackImplemented')} "
                    f"bumpSound={(blocked_movement_bump.get('bumpSound') or {}).get('source')}:"
                    f"{(blocked_movement_bump.get('bumpSound') or {}).get('soundKey')} "
                    f"bumpSoundSrc={(blocked_movement_bump.get('bumpSound') or {}).get('soundSrc')} "
                    f"bumpSoundPlayed={(blocked_movement_bump.get('bumpSound') or {}).get('soundPlayed')} "
                    f"bumpSoundCount={(blocked_movement_bump.get('soundCounts') or {}).get('menuMove')} "
                    f"browserBumpSound={(blocked_movement_bump.get('bumpSound') or {}).get('browserMovementBumpSoundImplemented')} "
                    f"originalObjectScriptCollisionRuntimeImplemented={blocked_movement_bump.get('originalObjectScriptCollisionRuntimeImplemented')}"
                ),
                "wallSlideFacing": (
                    f"map={wall_slide_direction.get('map')} "
                    f"from={((wall_slide_direction.get('attempt') or {}).get('tile') or {}).get('x')},"
                    f"{((wall_slide_direction.get('attempt') or {}).get('tile') or {}).get('y')} "
                    f"input={(((wall_slide_direction.get('attempt') or {}).get('input') or {}).get('name'))} "
                    f"actualDxDy={(((wall_slide_direction.get('attempt') or {}).get('movement') or {}).get('dx'))},"
                    f"{(((wall_slide_direction.get('attempt') or {}).get('movement') or {}).get('dy'))} "
                    f"target={((((wall_slide_direction.get('attempt') or {}).get('movement') or {}).get('nextTile') or {}).get('x'))},"
                    f"{((((wall_slide_direction.get('attempt') or {}).get('movement') or {}).get('nextTile') or {}).get('y'))} "
                    f"inputDir={wall_slide_direction.get('inputDir')} "
                    f"playerDir={wall_slide_direction.get('playerDirAfterInput')} "
                    f"expectedDir={wall_slide_direction.get('expectedDir')} "
                    f"movementAction={(wall_slide_direction.get('fieldMovementSound') or {}).get('movementAction')} "
                    f"stepSound={(wall_slide_direction.get('fieldMovementSound') or {}).get('soundKey')} "
                    f"stepSoundSrc={(wall_slide_direction.get('fieldMovementSound') or {}).get('soundSrc')} "
                    f"browserWallSlideFacing={wall_slide_direction.get('wallSlideActualDirectionImplemented')} "
                    f"originalWallSlideFacingProofImplemented={wall_slide_direction.get('originalWallSlideFacingProofImplemented')}"
                ),
                "partyTrailAnimation": (
                    f"map={party_trail_animation.get('map')} "
                    f"members={','.join(party_trail_animation.get('partyMembers') or [])} "
                    f"actorCollision={party_trail_animation.get('actorCollisionEnabled')} "
                    f"overlapMoveAllowed={party_trail_animation.get('overlapMoveAllowed')} "
                    f"movingSamples={party_trail_animation.get('followerMovingSampleCount')} "
                    f"laggingTurn={bool(party_trail_animation.get('laggingTurnSample'))}"
                ),
                "fieldPoison": (
                    f"map={field_poison.get('map')} "
                    f"interval={field_poison.get('fieldPoisonStepInterval')} "
                    f"steps={len(field_poison.get('movementSteps') or [])} "
                    f"hp={(field_poison.get('fieldStatusEffect') or {}).get('effects', [{}])[0].get('hpBefore')}->"
                    f"{(field_poison.get('fieldStatusEffect') or {}).get('effects', [{}])[0].get('hpAfter')} "
                    f"damage={(field_poison.get('fieldStatusEffect') or {}).get('effects', [{}])[0].get('damage')} "
                    f"partialCounter={(((field_poison.get('movementSteps') or [None, {}])[1] or {}).get('savedAfterStepAtaho') or {}).get('fieldPoisonStepCount')} "
                    f"partialAutoSource={(((field_poison.get('movementSteps') or [None, {}])[1] or {}).get('fieldStatusStepAutoSave') or {}).get('source')} "
                    f"progressCount={(field_poison.get('progress') or {}).get('counts', {}).get('field-status-effect-prototype')} "
                    f"autoSaved={(field_poison.get('fieldStatusAutoSave') or {}).get('saved')} "
                    f"autoSource={(field_poison.get('fieldStatusAutoSave') or {}).get('source')} "
                    f"savedHp={(field_poison.get('savedAtaho') or {}).get('hp')} "
                    f"savedPoison={'poison' in ((field_poison.get('savedAtaho') or {}).get('statuses') or [])} "
                    f"objective={(field_poison.get('objectiveBefore') or {}).get('title')} "
                    f"objectiveAction={(field_poison.get('objectiveAction') or {}).get('action')} "
                    f"objectiveActiveId={(field_poison.get('objectiveAction') or {}).get('activeId')} "
                    f"partyHud={'Ataho HP 34/36 독' in ' '.join(str(line) for line in (field_poison.get('playHudLines') or []))} "
                    f"hud={'상태 Ataho 독 -2 HP 34' in ' '.join(str(line) for line in (field_poison.get('playHudLines') or []))} "
                    f"statusFeedback={((field_poison.get('fieldStatusFeedbackLast') or {}).get('source') or '')}:"
                    f"{((field_poison.get('fieldStatusFeedbackLast') or {}).get('text') or '')} "
                    f"statusFeedbackRender={bool(field_poison.get('fieldStatusFeedbackRender') or [])} "
                    f"statusSound={((field_poison.get('fieldStatusFeedbackLast') or {}).get('fieldStatusSound') or '')} "
                    f"statusSoundSrc={((field_poison.get('fieldStatusFeedbackLast') or {}).get('fieldStatusSoundSrc') or '')} "
                    f"statusSoundPlayed={((field_poison.get('fieldStatusFeedbackLast') or {}).get('fieldStatusSoundPlayed'))} "
                    f"noticeFeedback={((field_poison.get('fieldStatusNoticeFeedbackLast') or {}).get('source') or '')}:"
                    f"{((field_poison.get('fieldStatusNoticeFeedbackLast') or {}).get('statusSource') or '')}:"
                    f"{((field_poison.get('fieldStatusNoticeFeedbackLast') or {}).get('text') or '')} "
                    f"noticeFeedbackRender={bool(field_poison.get('fieldStatusNoticeFeedbackRender') or [])} "
                    f"noticeSound={((field_poison.get('fieldStatusNoticeFeedbackLast') or {}).get('fieldStatusSound') or '')} "
                    f"noticeSoundSrc={((field_poison.get('fieldStatusNoticeFeedbackLast') or {}).get('fieldStatusSoundSrc') or '')} "
                    f"noticeSoundPlayed={((field_poison.get('fieldStatusNoticeFeedbackLast') or {}).get('fieldStatusSoundPlayed'))} "
                    f"originalStatusFormulaImplemented={field_poison.get('originalStatusFormulaImplemented')}"
                ),
                "fieldPoisonPartialContinue": (
                    f"titleContinue={field_poison_partial_continue.get('ok')} "
                    f"label={'이어하기 map1_02b 11,12' in ' '.join(str((item or {}).get('label') or '') for item in (field_poison_partial_continue.get('titleItemsBefore') or []))} "
                    f"statusLabel={'상태 독' in ' '.join(str((item or {}).get('label') or '') for item in (field_poison_partial_continue.get('titleItemsBefore') or []))} "
                    f"map={field_poison_partial_continue.get('map')} "
                    f"tile={(field_poison_partial_continue.get('foot') or {}).get('x')},{(field_poison_partial_continue.get('foot') or {}).get('y')} "
                    f"savedCounter={(field_poison_partial_continue.get('savedBeforeTitleAtaho') or {}).get('fieldPoisonStepCount')} "
                    f"restoredCounter={(field_poison_partial_continue.get('restoredAtaho') or {}).get('fieldPoisonStepCount')} "
                    f"restoredHp={(field_poison_partial_continue.get('restoredAtaho') or {}).get('hp')} "
                    f"restoredPoison={'poison' in ((field_poison_partial_continue.get('restoredAtaho') or {}).get('statuses') or [])} "
                    f"autoSource={(field_poison_partial_continue.get('stepAutoSave') or {}).get('source')} "
                    f"quickLoadText={field_poison_partial_continue.get('quickLoadText')} "
                    f"hud={'Ataho HP 36/36 독' in ' '.join(str(line) for line in (field_poison_partial_continue.get('playHudLines') or []))} "
                    f"statusMenu={'필드 독 2/4' in ' '.join(str(line) for line in (field_poison_partial_continue.get('statusMenuLines') or []))} "
                    f"statusFeedback={((field_poison_partial_continue.get('fieldStatusFeedbackLast') or {}).get('source') or '')}:"
                    f"{((field_poison_partial_continue.get('fieldStatusFeedbackLast') or {}).get('text') or '')} "
                    f"statusFeedbackRender={bool(field_poison_partial_continue.get('fieldStatusFeedbackRender') or [])} "
                    f"originalStatusFormulaImplemented={field_poison_partial_continue.get('originalStatusFormulaImplemented')}"
                ),
                "fieldPoisonCureContinue": (
                    f"titleContinue={field_poison_cure_continue.get('ok')} "
                    f"label={'이어하기 map1_02b 11,12' in ' '.join(str((item or {}).get('label') or '') for item in (field_poison_cure_continue.get('titleItemsBefore') or []))} "
                    f"statusLabel={'상태 독' in ' '.join(str((item or {}).get('label') or '') for item in (field_poison_cure_continue.get('titleItemsBefore') or []))} "
                    f"map={field_poison_cure_continue.get('map')} "
                    f"tile={(field_poison_cure_continue.get('foot') or {}).get('x')},{(field_poison_cure_continue.get('foot') or {}).get('y')} "
                    f"count={(field_poison_cure_continue.get('itemUse') or {}).get('countBefore')}->{(field_poison_cure_continue.get('itemUse') or {}).get('countAfter')} "
                    f"counter={(field_poison_cure_continue.get('itemUse') or {}).get('fieldPoisonStepCountBefore')}->{(field_poison_cure_continue.get('itemUse') or {}).get('fieldPoisonStepCountAfter')} "
                    f"savedCounter={(field_poison_cure_continue.get('savedBeforeTitleAtaho') or {}).get('fieldPoisonStepCount')} "
                    f"restoredCounter={(field_poison_cure_continue.get('restoredAtaho') or {}).get('fieldPoisonStepCount')} "
                    f"restoredPoison={'poison' in ((field_poison_cure_continue.get('restoredAtaho') or {}).get('statuses') or [])} "
                    f"autoSource={(field_poison_cure_continue.get('itemAutoSave') or {}).get('source')} "
                    f"quickLoadText={field_poison_cure_continue.get('quickLoadText')} "
                    f"hud={'독' in ' '.join(str(line) for line in (field_poison_cure_continue.get('playHudLines') or []))} "
                    f"statusMenu={'필드 독' in ' '.join(str(line) for line in (field_poison_cure_continue.get('statusMenuLines') or []))} "
                    f"statusFeedbackCleared={field_poison_cure_continue.get('staleFeedbackCountAfterUse') == 0 and field_poison_cure_continue.get('staleFeedbackAfterUse') is None} "
                    f"originalStatusFormulaImplemented={field_poison_cure_continue.get('originalStatusFormulaImplemented')}"
                ),
                "fieldPoisonEffectContinue": (
                    f"titleContinue={field_poison_effect_continue.get('ok')} "
                    f"label={'이어하기 map1_02b 11,12' in ' '.join(str((item or {}).get('label') or '') for item in (field_poison_effect_continue.get('titleItemsBefore') or []))} "
                    f"statusLabel={'상태 독' in ' '.join(str((item or {}).get('label') or '') for item in (field_poison_effect_continue.get('titleItemsBefore') or []))} "
                    f"map={field_poison_effect_continue.get('map')} "
                    f"tile={(field_poison_effect_continue.get('foot') or {}).get('x')},{(field_poison_effect_continue.get('foot') or {}).get('y')} "
                    f"savedHp={(field_poison_effect_continue.get('savedBeforeTitleAtaho') or {}).get('hp')} "
                    f"restoredHp={(field_poison_effect_continue.get('restoredAtaho') or {}).get('hp')} "
                    f"restoredPoison={'poison' in ((field_poison_effect_continue.get('restoredAtaho') or {}).get('statuses') or [])} "
                    f"autoSource={(field_poison_effect_continue.get('fieldStatusAutoSave') or {}).get('source')} "
                    f"objective={(field_poison_effect_continue.get('objectiveAfterContinue') or {}).get('title')} "
                    f"objectiveAction={(field_poison_effect_continue.get('objectiveAction') or {}).get('action')} "
                    f"objectiveActiveId={(field_poison_effect_continue.get('objectiveAction') or {}).get('activeId')} "
                    f"quickLoadText={field_poison_effect_continue.get('quickLoadText')} "
                    f"hud={'Ataho HP 34/36 독' in ' '.join(str(line) for line in (field_poison_effect_continue.get('playHudLines') or []))} "
                    f"noticeFeedback={((field_poison_effect_continue.get('fieldStatusNoticeFeedbackLast') or {}).get('source') or '')}:"
                    f"{((field_poison_effect_continue.get('fieldStatusNoticeFeedbackLast') or {}).get('statusSource') or '')}:"
                    f"{((field_poison_effect_continue.get('fieldStatusNoticeFeedbackLast') or {}).get('text') or '')} "
                    f"noticeFeedbackRender={bool(field_poison_effect_continue.get('fieldStatusNoticeFeedbackRender') or [])} "
                    f"noticeSound={((field_poison_effect_continue.get('fieldStatusNoticeFeedbackLast') or {}).get('fieldStatusSound') or '')} "
                    f"noticeSoundSrc={((field_poison_effect_continue.get('fieldStatusNoticeFeedbackLast') or {}).get('fieldStatusSoundSrc') or '')} "
                    f"noticeSoundPlayed={((field_poison_effect_continue.get('fieldStatusNoticeFeedbackLast') or {}).get('fieldStatusSoundPlayed'))} "
                    f"originalStatusFormulaImplemented={field_poison_effect_continue.get('originalStatusFormulaImplemented')}"
                ),
                "fieldParalysisCureContinue": (
                    f"titleContinue={field_paralysis_cure_continue.get('ok')} "
                    f"label={'이어하기 map1_02b 11,12' in ' '.join(str((item or {}).get('label') or '') for item in (field_paralysis_cure_continue.get('titleItemsBefore') or []))} "
                    f"statusLabel={'상태 마비' in ' '.join(str((item or {}).get('label') or '') for item in (field_paralysis_cure_continue.get('titleItemsBefore') or []))} "
                    f"map={field_paralysis_cure_continue.get('map')} "
                    f"tile={(field_paralysis_cure_continue.get('foot') or {}).get('x')},{(field_paralysis_cure_continue.get('foot') or {}).get('y')} "
                    f"count={(field_paralysis_cure_continue.get('itemUse') or {}).get('countBefore')}->{(field_paralysis_cure_continue.get('itemUse') or {}).get('countAfter')} "
                    f"counter={(field_paralysis_cure_continue.get('itemUse') or {}).get('fieldParalysisMoveCountBefore')}->{(field_paralysis_cure_continue.get('itemUse') or {}).get('fieldParalysisMoveCountAfter')} "
                    f"savedCounter={(field_paralysis_cure_continue.get('savedBeforeTitleAtaho') or {}).get('fieldParalysisMoveCount')} "
                    f"restoredCounter={(field_paralysis_cure_continue.get('restoredAtaho') or {}).get('fieldParalysisMoveCount')} "
                    f"restoredParalysis={'paralysis' in ((field_paralysis_cure_continue.get('restoredAtaho') or {}).get('statuses') or [])} "
                    f"autoSource={(field_paralysis_cure_continue.get('itemAutoSave') or {}).get('source')} "
                    f"quickLoadText={field_paralysis_cure_continue.get('quickLoadText')} "
                    f"hud={'마비' in ' '.join(str(line) for line in (field_paralysis_cure_continue.get('playHudLines') or []))} "
                    f"statusMenu={'필드 마비' in ' '.join(str(line) for line in (field_paralysis_cure_continue.get('statusMenuLines') or []))} "
                    f"staleCleared={field_paralysis_cure_continue.get('staleBlockAfterUse') is None and field_paralysis_cure_continue.get('staleStepAfterUse') is None} "
                    f"statusFeedbackCleared={field_paralysis_cure_continue.get('staleFeedbackCountAfterUse') == 0 and field_paralysis_cure_continue.get('staleFeedbackAfterUse') is None} "
                    f"originalStatusFormulaImplemented={field_paralysis_cure_continue.get('originalStatusFormulaImplemented')}"
                ),
                "fieldParalysisMovement": (
                    f"titleContinue={field_paralysis_movement.get('ok')} "
                    f"label={'이어하기 map1_02b 11,12' in ' '.join(str((item or {}).get('label') or '') for item in (field_paralysis_movement.get('titleItemsBefore') or []))} "
                    f"statusLabel={'상태 마비' in ' '.join(str((item or {}).get('label') or '') for item in (field_paralysis_movement.get('titleItemsBefore') or []))} "
                    f"attempts={len(field_paralysis_movement.get('attempts') or [])} "
                    f"blocked={((field_paralysis_movement.get('movementBlock') or {}).get('blockCount') or 0)} "
                    f"tile={(field_paralysis_movement.get('foot') or {}).get('x')},{(field_paralysis_movement.get('foot') or {}).get('y')} "
                    f"savedCounter={(field_paralysis_movement.get('savedBeforeTitleAtaho') or {}).get('fieldParalysisMoveCount')} "
                    f"restoredCounter={(field_paralysis_movement.get('restoredAtaho') or {}).get('fieldParalysisMoveCount')} "
                    f"restoredParalysis={'paralysis' in ((field_paralysis_movement.get('restoredAtaho') or {}).get('statuses') or [])} "
                    f"autoSource={(field_paralysis_movement.get('movementBlockAutoSave') or {}).get('source')} "
                    f"objective={(field_paralysis_movement.get('objectiveAfterContinue') or {}).get('title')} "
                    f"objectiveAction={(field_paralysis_movement.get('objectiveAction') or {}).get('action')} "
                    f"objectiveActiveId={(field_paralysis_movement.get('objectiveAction') or {}).get('activeId')} "
                    f"quickLoadText={field_paralysis_movement.get('quickLoadText')} "
                    f"hud={'상태 Ataho 마비 이동 실패' in ' '.join(str(line) for line in (field_paralysis_movement.get('playHudLines') or []))} "
                    f"statusMenu={'상태 마비' in ' '.join(str(line) for line in (field_paralysis_movement.get('statusMenuLines') or []))} "
                    f"statusFeedback={((field_paralysis_movement.get('movementFeedbackLast') or {}).get('source') or '')}:"
                    f"{((field_paralysis_movement.get('movementFeedbackLast') or {}).get('text') or '')} "
                    f"statusFeedbackRender={bool(field_paralysis_movement.get('movementFeedbackRender') or [])} "
                    f"statusSound={((field_paralysis_movement.get('movementFeedbackLast') or {}).get('fieldStatusSound') or '')} "
                    f"statusSoundSrc={((field_paralysis_movement.get('movementFeedbackLast') or {}).get('fieldStatusSoundSrc') or '')} "
                    f"statusSoundPlayed={((field_paralysis_movement.get('movementFeedbackLast') or {}).get('fieldStatusSoundPlayed'))} "
                    f"noticeFeedback={((field_paralysis_movement.get('fieldStatusNoticeFeedbackLast') or {}).get('source') or '')}:"
                    f"{((field_paralysis_movement.get('fieldStatusNoticeFeedbackLast') or {}).get('statusSource') or '')}:"
                    f"{((field_paralysis_movement.get('fieldStatusNoticeFeedbackLast') or {}).get('text') or '')} "
                    f"noticeFeedbackRender={bool(field_paralysis_movement.get('fieldStatusNoticeFeedbackRender') or [])} "
                    f"noticeSound={((field_paralysis_movement.get('fieldStatusNoticeFeedbackLast') or {}).get('fieldStatusSound') or '')} "
                    f"noticeSoundSrc={((field_paralysis_movement.get('fieldStatusNoticeFeedbackLast') or {}).get('fieldStatusSoundSrc') or '')} "
                    f"noticeSoundPlayed={((field_paralysis_movement.get('fieldStatusNoticeFeedbackLast') or {}).get('fieldStatusSoundPlayed'))} "
                    f"originalStatusFormulaImplemented={field_paralysis_movement.get('originalStatusFormulaImplemented')}"
                ),
                "fieldParalysisPartialContinue": (
                    f"titleContinue={field_paralysis_partial_continue.get('ok')} "
                    f"label={'이어하기 map1_02b 11,12' in ' '.join(str((item or {}).get('label') or '') for item in (field_paralysis_partial_continue.get('titleItemsBefore') or []))} "
                    f"statusLabel={'상태 마비' in ' '.join(str((item or {}).get('label') or '') for item in (field_paralysis_partial_continue.get('titleItemsBefore') or []))} "
                    f"map={field_paralysis_partial_continue.get('map')} "
                    f"tile={(field_paralysis_partial_continue.get('foot') or {}).get('x')},{(field_paralysis_partial_continue.get('foot') or {}).get('y')} "
                    f"savedCounter={(field_paralysis_partial_continue.get('savedBeforeTitleAtaho') or {}).get('fieldParalysisMoveCount')} "
                    f"restoredCounter={(field_paralysis_partial_continue.get('restoredAtaho') or {}).get('fieldParalysisMoveCount')} "
                    f"restoredParalysis={'paralysis' in ((field_paralysis_partial_continue.get('restoredAtaho') or {}).get('statuses') or [])} "
                    f"autoSource={(field_paralysis_partial_continue.get('movementStepAutoSave') or {}).get('source')} "
                    f"quickLoadText={field_paralysis_partial_continue.get('quickLoadText')} "
                    f"hud={'Ataho HP 36/36 마비' in ' '.join(str(line) for line in (field_paralysis_partial_continue.get('playHudLines') or []))} "
                    f"statusMenu={'필드 마비 2/3' in ' '.join(str(line) for line in (field_paralysis_partial_continue.get('statusMenuLines') or []))} "
                    f"statusFeedback={((field_paralysis_partial_continue.get('movementFeedbackLast') or {}).get('source') or '')}:"
                    f"{((field_paralysis_partial_continue.get('movementFeedbackLast') or {}).get('text') or '')} "
                    f"statusFeedbackRender={bool(field_paralysis_partial_continue.get('movementFeedbackRender') or [])} "
                    f"originalStatusFormulaImplemented={field_paralysis_partial_continue.get('originalStatusFormulaImplemented')}"
                ),
                "checksum": checksum,
                "snapshots": {
                    "trace": trace,
                    "movementStepSound": movement_step_sound,
                    "movementBump": blocked_movement_bump,
                    "fieldPoison": field_poison,
                    "fieldPoisonPartialContinue": field_poison_partial_continue,
                    "fieldPoisonCureContinue": field_poison_cure_continue,
                    "fieldParalysisCureContinue": field_paralysis_cure_continue,
                    "fieldPoisonEffectContinue": field_poison_effect_continue,
                    "fieldParalysisMovement": field_paralysis_movement,
                    "fieldParalysisPartialContinue": field_paralysis_partial_continue,
                },
            }
            write_report(report)
            print(f"ok candidate movement browser trace={report['trace']} movementStepSound={report['movementStepSound']} movementBump={report['movementBump']} fieldPoison={report['fieldPoison']} fieldPoisonPartialContinue={report['fieldPoisonPartialContinue']} fieldPoisonCureContinue={report['fieldPoisonCureContinue']} fieldParalysisCureContinue={report['fieldParalysisCureContinue']} fieldPoisonEffectContinue={report['fieldPoisonEffectContinue']} fieldParalysisMovement={report['fieldParalysisMovement']} fieldParalysisPartialContinue={report['fieldParalysisPartialContinue']} checksum={checksum}")
        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()
