"""Probe one-shot counterfactual actions from reproducible baseline states."""

from __future__ import annotations

import argparse
import json
import sys
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from statistics import mean
from typing import Callable

import envpool
import numpy as np


REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(REPO_ROOT))

from pong_policy import ACTION_NAMES, DOWN, NOOP, UP, RamPolicy
from pong_state import PongState, detect_state


TriggerFn = Callable[["StepContext"], bool]


@dataclass(frozen=True)
class StepContext:
    step: int
    state: PongState
    dx: int | None
    dy: int | None
    action: int
    target_y: float
    mode: str
    previous_paddle_delta: int | None


@dataclass(frozen=True)
class Variant:
    name: str
    trigger: TriggerFn | None
    override_action: int | None
    horizon: int = 1
    max_interventions: int = 1


def reset_env(env: object) -> tuple[np.ndarray, dict]:
    reset_out = env.reset()
    if isinstance(reset_out, tuple):
        obs, info = reset_out
    else:
        obs, info = reset_out, {}
    return np.asarray(obs), info


def step_env(env: object, action: int) -> tuple[np.ndarray, float, bool, dict]:
    step_out = env.step(np.array([action], dtype=np.int32))
    if len(step_out) == 5:
        obs, reward, terminated, truncated, info = step_out
        done = np.logical_or(terminated, truncated)
    else:
        obs, reward, done, info = step_out
    return (
        np.asarray(obs),
        float(np.asarray(reward).reshape(-1)[0]),
        bool(np.asarray(done).reshape(-1)[0]),
        info,
    )


def sign(value: int | None) -> int:
    if value is None:
        return 0
    if value < 0:
        return -1
    if value > 0:
        return 1
    return 0


def trigger_dy4_noop_prev_down(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dy == 4
        and ctx.dx > 0
        and 184 <= ctx.state.ball_x <= 195
        and ctx.action == NOOP
        and ctx.previous_paddle_delta is not None
        and ctx.previous_paddle_delta > 0
    )


def trigger_dy8_down_prev_up(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dy == 8
        and ctx.dx > 0
        and 184 <= ctx.state.ball_x <= 195
        and ctx.action == DOWN
        and ctx.previous_paddle_delta is not None
        and ctx.previous_paddle_delta < 0
    )


def trigger_low_dy8_down_prev_up(ctx: StepContext) -> bool:
    return trigger_dy8_down_prev_up(ctx) and 120 <= ctx.state.ball_y <= 150


def trigger_opening_low_dy8_down_prev_up(ctx: StepContext) -> bool:
    return (
        trigger_low_dy8_down_prev_up(ctx)
        and ctx.state.self_score == 0
        and ctx.state.opponent_score == 0
        and 184 <= ctx.state.ball_x <= 188
        and 130 <= ctx.state.ball_y <= 145
        and 128 <= ctx.state.self_paddle_y <= 142
        and ctx.previous_paddle_delta is not None
        and -15 <= ctx.previous_paddle_delta <= -8
    )


def trigger_dyneg8_noop_prev_up(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dy == -8
        and ctx.dx > 0
        and 184 <= ctx.state.ball_x <= 195
        and ctx.action == NOOP
        and ctx.previous_paddle_delta is not None
        and ctx.previous_paddle_delta < 0
    )


def trigger_low_serve_late_up(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -4
        and 189 <= ctx.state.ball_x <= 201
        and 55 <= ctx.state.ball_y <= 67
        and 35 <= ctx.state.self_paddle_y <= 65
        and ctx.target_y == 50
    )


def trigger_low_serve_late_down(ctx: StepContext) -> bool:
    return trigger_low_serve_late_up(ctx) and ctx.action == DOWN


def trigger_low_serve_late_noop(ctx: StepContext) -> bool:
    return trigger_low_serve_late_up(ctx) and ctx.action == NOOP


def trigger_y86_mid_down(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and 196 <= ctx.state.ball_x <= 202
        and 78 <= ctx.state.ball_y <= 90
        and 60 <= ctx.state.self_paddle_y <= 90
        and ctx.state.self_paddle_y - ctx.target_y <= -4
    )


def trigger_y86_mid_down_noop(ctx: StepContext) -> bool:
    return trigger_y86_mid_down(ctx) and ctx.action == NOOP


def trigger_y86_mid_down_up(ctx: StepContext) -> bool:
    return trigger_y86_mid_down(ctx) and ctx.action == UP


def trigger_y86_mid_down_up_prev_down(ctx: StepContext) -> bool:
    return (
        trigger_y86_mid_down_up(ctx)
        and ctx.previous_paddle_delta is not None
        and ctx.previous_paddle_delta > 0
    )


def trigger_score00_mid_down_y154_final_up(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and ctx.state.self_score == 0
        and ctx.state.opponent_score == 0
        and 198 <= ctx.state.ball_x <= 204
        and 148 <= ctx.state.ball_y <= 156
        and 150 <= ctx.target_y <= 156
        and 150 <= ctx.state.self_paddle_y <= 170
        and ctx.action == UP
    )


def trigger_y86_mid_down_any_prev_up(ctx: StepContext) -> bool:
    return (
        trigger_y86_mid_down(ctx)
        and ctx.previous_paddle_delta is not None
        and ctx.previous_paddle_delta < 0
    )


def trigger_y86_pre_contact(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and 186 <= ctx.state.ball_x <= 194
        and 68 <= ctx.state.ball_y <= 82
        and 65 <= ctx.state.self_paddle_y <= 90
    )


def trigger_y86_pre_contact_up(ctx: StepContext) -> bool:
    return trigger_y86_pre_contact(ctx) and ctx.action == UP


def trigger_y86_pre_contact_noop(ctx: StepContext) -> bool:
    return trigger_y86_pre_contact(ctx) and ctx.action == NOOP


def trigger_y86_late_up_below_target(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and 196 <= ctx.state.ball_x <= 200
        and 80 <= ctx.state.ball_y <= 84
        and 84 <= ctx.state.self_paddle_y <= 92
        and 80 <= ctx.target_y <= 84
        and 4 <= ctx.state.self_paddle_y - ctx.target_y <= 10
        and ctx.action == UP
    )


def trigger_y86_final_down(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and 200 <= ctx.state.ball_x <= 204
        and 84 <= ctx.state.ball_y <= 88
        and 76 <= ctx.state.self_paddle_y <= 82
        and 84 <= ctx.target_y <= 88
        and ctx.action == DOWN
    )


def trigger_y86_setup_offset8_up(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and 172 <= ctx.state.ball_x <= 176
        and 56 <= ctx.state.ball_y <= 60
        and 84 <= ctx.state.self_paddle_y <= 90
        and 74 <= ctx.target_y <= 78
        and ctx.action == UP
    )


def trigger_y86_setup_offset8_up_opp2(ctx: StepContext) -> bool:
    return trigger_y86_setup_offset8_up(ctx) and ctx.state.opponent_score == 2


def trigger_y86_setup_offset8_up_tied22(ctx: StepContext) -> bool:
    return (
        trigger_y86_setup_offset8_up(ctx)
        and ctx.state.self_score == 2
        and ctx.state.opponent_score == 2
    )


def trigger_y86_setup_offset8_up_score20(ctx: StepContext) -> bool:
    return (
        trigger_y86_setup_offset8_up(ctx)
        and ctx.state.self_score == 2
        and ctx.state.opponent_score == 0
    )


def trigger_y86_score20_approach_x82_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 2
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -4
        and 80 <= ctx.state.ball_x <= 84
        and 123 <= ctx.state.ball_y <= 127
        and 80 <= ctx.state.self_paddle_y <= 84
        and 76 <= ctx.target_y <= 78
        and ctx.action == UP
    )


def trigger_y86_score20_serve_initial_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 2
        and ctx.state.opponent_score == 0
        and ctx.mode == "initial"
        and 124 <= ctx.state.ball_x <= 128
        and 124 <= ctx.state.ball_y <= 128
        and 96 <= ctx.state.self_paddle_y <= 106
        and ctx.action == DOWN
    )


def trigger_y86_score60_serve_initial_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 6
        and ctx.state.opponent_score == 0
        and ctx.mode == "initial"
        and 124 <= ctx.state.ball_x <= 128
        and 124 <= ctx.state.ball_y <= 128
        and 96 <= ctx.state.self_paddle_y <= 106
        and ctx.action == DOWN
    )


def trigger_y86_score100_serve_initial_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 10
        and ctx.state.opponent_score == 0
        and ctx.mode == "initial"
        and 124 <= ctx.state.ball_x <= 128
        and 124 <= ctx.state.ball_y <= 128
        and 96 <= ctx.state.self_paddle_y <= 106
        and ctx.action == DOWN
    )


def trigger_y86_score140_serve_initial_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 14
        and ctx.state.opponent_score == 0
        and ctx.mode == "initial"
        and 124 <= ctx.state.ball_x <= 128
        and 124 <= ctx.state.ball_y <= 128
        and 96 <= ctx.state.self_paddle_y <= 106
        and ctx.action == DOWN
    )


def trigger_y86_setup_offset8_up_score42(ctx: StepContext) -> bool:
    return (
        trigger_y86_setup_offset8_up(ctx)
        and ctx.state.self_score == 4
        and ctx.state.opponent_score == 2
    )


def trigger_y86_setup_offset8_up_score62(ctx: StepContext) -> bool:
    return (
        trigger_y86_setup_offset8_up(ctx)
        and ctx.state.self_score == 6
        and ctx.state.opponent_score == 2
    )


def trigger_y86_setup_offset8_up_score62_early_step(ctx: StepContext) -> bool:
    return trigger_y86_setup_offset8_up_score62(ctx) and 1500 <= ctx.step <= 1700


def trigger_y86_setup_offset8_up_score82(ctx: StepContext) -> bool:
    return (
        trigger_y86_setup_offset8_up(ctx)
        and ctx.state.self_score == 8
        and ctx.state.opponent_score == 2
    )


def trigger_y86_setup_offset8_up_score82_early_step(ctx: StepContext) -> bool:
    return trigger_y86_setup_offset8_up_score82(ctx) and 2050 <= ctx.step <= 2110


def trigger_y86_setup_offset6_down(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and 180 <= ctx.state.ball_x <= 184
        and 64 <= ctx.state.ball_y <= 68
        and 66 <= ctx.state.self_paddle_y <= 72
        and 74 <= ctx.target_y <= 78
        and ctx.action == DOWN
    )


def trigger_y86_bounce_x166_down(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 5
        and 164 <= ctx.state.ball_x <= 168
        and 48 <= ctx.state.ball_y <= 52
        and 76 <= ctx.state.self_paddle_y <= 80
        and 80 <= ctx.target_y <= 85
        and ctx.action == DOWN
    )


def trigger_y86_bounce_score22_x166_down(ctx: StepContext) -> bool:
    return (
        trigger_y86_bounce_x166_down(ctx)
        and ctx.state.self_score == 2
        and ctx.state.opponent_score == 2
    )


def trigger_y86_bounce_not_score60_x166_down(ctx: StepContext) -> bool:
    return (
        trigger_y86_bounce_x166_down(ctx)
        and not (ctx.state.self_score == 6 and ctx.state.opponent_score == 0)
    )


def trigger_y86_bounce_score60_x166_down(ctx: StepContext) -> bool:
    return (
        trigger_y86_bounce_x166_down(ctx)
        and ctx.state.self_score == 6
        and ctx.state.opponent_score == 0
    )


def trigger_high_down_pre_contact(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 187 <= ctx.state.ball_x <= 195
        and 145 <= ctx.state.ball_y <= 170
        and 150 <= ctx.state.self_paddle_y <= 185
    )


def trigger_high_down_pre_contact_up(ctx: StepContext) -> bool:
    return trigger_high_down_pre_contact(ctx) and ctx.action == UP


def trigger_high_down_pre_contact_noop(ctx: StepContext) -> bool:
    return trigger_high_down_pre_contact(ctx) and ctx.action == NOOP


def trigger_high_down_y165_offset4_up(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 188 <= ctx.state.ball_x <= 192
        and 139 <= ctx.state.ball_y <= 143
        and 152 <= ctx.state.self_paddle_y <= 158
        and 143 <= ctx.target_y <= 147
        and ctx.action == UP
    )


def trigger_high_down_y165_x166_down(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 164 <= ctx.state.ball_x <= 168
        and 89 <= ctx.state.ball_y <= 97
        and 140 <= ctx.target_y <= 152
        and 120 <= ctx.state.self_paddle_y <= 150
        and ctx.action == DOWN
    )


def trigger_high_down_y165_x150_up(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 148 <= ctx.state.ball_x <= 152
        and 57 <= ctx.state.ball_y <= 65
        and 143 <= ctx.target_y <= 147
        and 160 <= ctx.state.self_paddle_y <= 170
        and ctx.action == UP
    )


def trigger_high_down_y165_bounce_x146_down(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 9
        and 144 <= ctx.state.ball_x <= 148
        and 50 <= ctx.state.ball_y <= 56
        and 154 <= ctx.target_y <= 162
        and 143 <= ctx.state.self_paddle_y <= 150
        and ctx.action == DOWN
    )


def trigger_high_down_y165_bounce_score40_x146_down(ctx: StepContext) -> bool:
    return (
        trigger_high_down_y165_bounce_x146_down(ctx)
        and ctx.state.self_score == 4
        and ctx.state.opponent_score == 0
    )


def trigger_high_down_y165_bounce_score80_x146_down(ctx: StepContext) -> bool:
    return (
        trigger_high_down_y165_bounce_x146_down(ctx)
        and ctx.state.self_score == 8
        and ctx.state.opponent_score == 0
    )


def trigger_high_down_y165_bounce_score120_x146_down(ctx: StepContext) -> bool:
    return (
        trigger_high_down_y165_bounce_x146_down(ctx)
        and ctx.state.self_score == 12
        and ctx.state.opponent_score == 0
    )


def trigger_high_down_y165_bounce_score160_x146_down(ctx: StepContext) -> bool:
    return (
        trigger_high_down_y165_bounce_x146_down(ctx)
        and ctx.state.self_score == 16
        and ctx.state.opponent_score == 0
    )


def trigger_high_down_y165_bounce_score181_x146_down(ctx: StepContext) -> bool:
    return (
        trigger_high_down_y165_bounce_x146_down(ctx)
        and ctx.state.self_score == 18
        and ctx.state.opponent_score == 1
    )


def trigger_high_down_y165_bounce_score202_x146_down(ctx: StepContext) -> bool:
    return (
        trigger_high_down_y165_bounce_x146_down(ctx)
        and ctx.state.self_score == 20
        and ctx.state.opponent_score == 2
    )


def trigger_high_down_y165_initial_x166_down(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and ctx.state.self_score == 0
        and ctx.state.opponent_score == 0
        and 165 <= ctx.state.ball_x <= 167
        and 95 <= ctx.state.ball_y <= 99
        and 132 <= ctx.state.self_paddle_y <= 138
        and 147 <= ctx.target_y <= 151
        and ctx.previous_paddle_delta is not None
        and ctx.previous_paddle_delta <= -10
        and ctx.action == DOWN
    )


def trigger_high_down_score00_y173_bounce_noop(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 9
        and 142 <= ctx.state.ball_x <= 146
        and 51 <= ctx.state.ball_y <= 55
        and 160 <= ctx.state.self_paddle_y <= 166
        and 159 <= ctx.target_y <= 163
        and ctx.action == NOOP
    )


def trigger_high_down_score00_y173_x148_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 146 <= ctx.state.ball_x <= 150
        and 59 <= ctx.state.ball_y <= 63
        and 160 <= ctx.state.self_paddle_y <= 176
        and 147 <= ctx.target_y <= 151
        and ctx.action == UP
    )


def trigger_high_down_score00_y173_x164_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 162 <= ctx.state.ball_x <= 166
        and 91 <= ctx.state.ball_y <= 95
        and 141 <= ctx.state.self_paddle_y <= 145
        and 147 <= ctx.target_y <= 151
        and ctx.action == DOWN
    )


def trigger_high_down_score00_y173_final_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 202 <= ctx.state.ball_x <= 204
        and 171 <= ctx.state.ball_y <= 175
        and 161 <= ctx.state.self_paddle_y <= 165
        and 171 <= ctx.target_y <= 175
        and ctx.action == DOWN
    )


def trigger_high_down_score21_y171_x155_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 2
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 153 <= ctx.state.ball_x <= 157
        and 73 <= ctx.state.ball_y <= 77
        and 112 <= ctx.state.self_paddle_y <= 118
        and 147 <= ctx.target_y <= 151
        and ctx.action == DOWN
    )


def trigger_high_down_score21_y171_setup_x135_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 2
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -4
        and 133 <= ctx.state.ball_x <= 137
        and 119 <= ctx.state.ball_y <= 123
        and 134 <= ctx.state.self_paddle_y <= 138
        and ctx.target_y == 50
        and ctx.mode == "low_serve"
        and ctx.action == UP
    )


def trigger_high_down_score21_y171_setup_x167_noop(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 2
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -4
        and 165 <= ctx.state.ball_x <= 169
        and 87 <= ctx.state.ball_y <= 91
        and 46 <= ctx.state.self_paddle_y <= 50
        and ctx.target_y == 50
        and ctx.mode == "low_serve"
        and ctx.action == NOOP
    )


def trigger_high_down_score21_y171_setup_x183_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 2
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -4
        and 181 <= ctx.state.ball_x <= 185
        and 71 <= ctx.state.ball_y <= 75
        and 37 <= ctx.state.self_paddle_y <= 41
        and ctx.target_y == 50
        and ctx.mode == "low_serve"
        and ctx.action == DOWN
    )


def trigger_high_down_score21_y171_x187_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 2
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 185 <= ctx.state.ball_x <= 189
        and 137 <= ctx.state.ball_y <= 141
        and 156 <= ctx.state.self_paddle_y <= 162
        and 147 <= ctx.target_y <= 151
        and ctx.action == UP
    )


def trigger_high_down_score21_y171_final_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 2
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 201 <= ctx.state.ball_x <= 203
        and 169 <= ctx.state.ball_y <= 173
        and 141 <= ctx.state.self_paddle_y <= 145
        and 169 <= ctx.target_y <= 173
        and ctx.action == DOWN
    )


def trigger_high_down_score01_y203_x151_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and 149 <= ctx.state.ball_x <= 153
        and 149 <= ctx.state.ball_y <= 153
        and 152 <= ctx.state.self_paddle_y <= 158
        and 190 <= ctx.target_y <= 194
        and ctx.action == DOWN
    )


def trigger_high_down_score01_y203_x167_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and 165 <= ctx.state.ball_x <= 169
        and 165 <= ctx.state.ball_y <= 169
        and 176 <= ctx.state.self_paddle_y <= 182
        and 190 <= ctx.target_y <= 194
        and ctx.action == DOWN
    )


def trigger_high_down_score01_y203_final_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and 201 <= ctx.state.ball_x <= 203
        and 201 <= ctx.state.ball_y <= 203
        and 199 <= ctx.state.self_paddle_y <= 203
        and 194 <= ctx.target_y <= 198
        and ctx.action == UP
    )


def trigger_chain2_score00_initial_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 100
        and ctx.state.ball_x == 125
        and ctx.state.ball_y == 131
        and 83 <= ctx.state.self_paddle_y <= 87
        and 129 <= ctx.target_y <= 133
        and ctx.mode == "initial"
        and ctx.action == DOWN
    )


def trigger_chain3_score20_initial_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 2
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 60
        and ctx.state.ball_x == 125
        and ctx.state.ball_y == 125
        and ctx.state.self_paddle_y == 77
        and ctx.target_y == 125
        and ctx.mode == "initial"
        and ctx.action == DOWN
    )


def trigger_chain8_score11_initial_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 60
        and ctx.state.ball_x == 126
        and ctx.state.ball_y == 126
        and ctx.state.self_paddle_y == 85
        and ctx.target_y == 126
        and ctx.mode == "initial"
        and ctx.action == DOWN
    )


def trigger_chain8_score11_phase_x106_noop(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and ctx.state.ball_x == 106
        and ctx.state.ball_y == 97
        and ctx.state.self_paddle_y == 152
        and ctx.target_y == 151
        and ctx.mode == "intercept"
        and ctx.action == NOOP
    )


def trigger_chain8_score11_inactive_pad85_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 1
        and ctx.dx == 0
        and ctx.dy == 0
        and ctx.state.ball_x == 52
        and ctx.state.ball_y == 0
        and ctx.state.self_paddle_y == 85
        and ctx.target_y == 100
        and ctx.mode == "inactive"
        and ctx.action == DOWN
    )


def trigger_chain8_prev_terminal_y63_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 1
        and ctx.dx == 2
        and ctx.dy == -4
        and ctx.state.ball_x == 191
        and ctx.state.ball_y == 63
        and ctx.state.self_paddle_y == 60
        and ctx.target_y == 50
        and ctx.mode == "low_serve"
        and ctx.action == UP
    )


def trigger_mid_up_y164_late_noop(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -8
        and 193 <= ctx.state.ball_x <= 197
        and 176 <= ctx.state.ball_y <= 184
        and 175 <= ctx.state.self_paddle_y <= 185
        and ctx.action == NOOP
    )


def trigger_mid_up_y164_pre_up(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -8
        and 187 <= ctx.state.ball_x <= 191
        and 188 <= ctx.state.ball_y <= 196
        and 190 <= ctx.state.self_paddle_y <= 205
        and ctx.action == UP
    )


def trigger_mid_up_y164_precontact_noop(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -8
        and 187 <= ctx.state.ball_x <= 191
        and 188 <= ctx.state.ball_y <= 196
        and 200 <= ctx.state.self_paddle_y <= 205
        and 184 <= ctx.target_y <= 188
        and ctx.action == NOOP
        and ctx.mode == "mid_up_precontact_up_correction"
    )


def trigger_high_down_setup(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 121 <= ctx.state.ball_x <= 153
        and 103 <= ctx.state.ball_y <= 167
        and 155 <= ctx.state.self_paddle_y <= 195
        and 165 <= ctx.target_y <= 185
    )


def trigger_high_down_setup_up(ctx: StepContext) -> bool:
    return trigger_high_down_setup(ctx) and ctx.action == UP


def trigger_high_down_setup_down(ctx: StepContext) -> bool:
    return trigger_high_down_setup(ctx) and ctx.action == DOWN


def trigger_high_down_setup_noop(ctx: StepContext) -> bool:
    return trigger_high_down_setup(ctx) and ctx.action == NOOP


def trigger_late_low_loop_pre_up(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and 3 <= ctx.dy <= 4
        and 169 <= ctx.state.ball_x <= 177
        and 49 <= ctx.state.ball_y <= 57
        and 80 <= ctx.state.self_paddle_y <= 105
        and 60 <= ctx.target_y <= 75
        and ctx.action == UP
    )


def trigger_late_low_loop_down(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and 2 <= ctx.dy <= 4
        and 181 <= ctx.state.ball_x <= 187
        and 61 <= ctx.state.ball_y <= 67
        and 45 <= ctx.state.self_paddle_y <= 70
        and 65 <= ctx.target_y <= 75
        and ctx.action == DOWN
    )


def trigger_seed33_low_up_pre_down(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -8
        and 177 <= ctx.state.ball_x <= 185
        and 158 <= ctx.state.ball_y <= 174
        and 120 <= ctx.state.self_paddle_y <= 140
        and 135 <= ctx.target_y <= 150
        and ctx.action == DOWN
    )


def trigger_seed33_low_up_late_up(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -8
        and 189 <= ctx.state.ball_x <= 201
        and 126 <= ctx.state.ball_y <= 150
        and 130 <= ctx.state.self_paddle_y <= 175
        and 126 <= ctx.target_y <= 145
        and ctx.action == UP
    )


def trigger_low_up_y111_pre_up(ctx: StepContext) -> bool:
    return (
        ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -8
        and 187 <= ctx.state.ball_x <= 195
        and 127 <= ctx.state.ball_y <= 143
        and 130 <= ctx.state.self_paddle_y <= 150
        and 125 <= ctx.target_y <= 135
        and ctx.action == UP
    )


def trigger_seed33_low_up_score01_exact(ctx: StepContext) -> bool:
    return (
        trigger_seed33_low_up_pre_down(ctx)
        and ctx.state.self_score == 0
        and ctx.state.opponent_score == 1
        and 176 <= ctx.state.ball_x <= 178
        and 172 <= ctx.state.ball_y <= 176
        and 132 <= ctx.state.self_paddle_y <= 138
        and ctx.previous_paddle_delta is not None
        and ctx.previous_paddle_delta <= -15
    )


def trigger_low_up_score10_final_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -8
        and 202 <= ctx.state.ball_x <= 204
        and 118 <= ctx.state.ball_y <= 122
        and 113 <= ctx.state.self_paddle_y <= 117
        and 118 <= ctx.target_y <= 122
        and ctx.action == DOWN
    )


def trigger_low_up_score10_offset2_noop(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -8
        and 198 <= ctx.state.ball_x <= 202
        and 126 <= ctx.state.ball_y <= 130
        and 129 <= ctx.state.self_paddle_y <= 133
        and 126 <= ctx.target_y <= 130
        and ctx.action == NOOP
    )


def trigger_low_up_score00_y118_offset2_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -8
        and 199 <= ctx.state.ball_x <= 203
        and 124 <= ctx.state.ball_y <= 128
        and 145 <= ctx.state.self_paddle_y <= 155
        and 124 <= ctx.target_y <= 128
        and ctx.action == UP
    )


def trigger_low_up_score00_y118_offset3_noop(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -8
        and 195 <= ctx.state.ball_x <= 199
        and 132 <= ctx.state.ball_y <= 136
        and 132 <= ctx.state.self_paddle_y <= 138
        and 132 <= ctx.target_y <= 136
        and ctx.action == NOOP
    )


def trigger_low_up_score12_y54_final_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 2
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -4
        and 200 <= ctx.state.ball_x <= 204
        and 52 <= ctx.state.ball_y <= 56
        and 53 <= ctx.state.self_paddle_y <= 57
        and ctx.target_y == 50
        and ctx.action == UP
    )


def trigger_low_up_score12_y54_offset2_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 2
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -4
        and 196 <= ctx.state.ball_x <= 200
        and 56 <= ctx.state.ball_y <= 60
        and 45 <= ctx.state.self_paddle_y <= 49
        and ctx.target_y == 50
        and ctx.action == UP
    )


def trigger_low_up_score12_y54_offset3_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 2
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -4
        and 192 <= ctx.state.ball_x <= 196
        and 60 <= ctx.state.ball_y <= 64
        and 37 <= ctx.state.self_paddle_y <= 41
        and ctx.target_y == 50
        and ctx.action == DOWN
    )


def trigger_low_up_score12_y54_early_x150_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 2
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -4
        and 148 <= ctx.state.ball_x <= 152
        and 104 <= ctx.state.ball_y <= 108
        and 44 <= ctx.state.self_paddle_y <= 48
        and ctx.target_y == 50
        and ctx.action == DOWN
    )


def trigger_low_up_score12_y54_mid_x182_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 2
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -4
        and 180 <= ctx.state.ball_x <= 184
        and 72 <= ctx.state.ball_y <= 76
        and 59 <= ctx.state.self_paddle_y <= 63
        and ctx.target_y == 50
        and ctx.action == UP
    )


def trigger_high_down_score11_y205_x186_noop(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 184 <= ctx.state.ball_x <= 188
        and 171 <= ctx.state.ball_y <= 175
        and 182 <= ctx.state.self_paddle_y <= 186
        and 183 <= ctx.target_y <= 187
        and ctx.action == NOOP
    )


def trigger_high_down_score11_y205_x194_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 192 <= ctx.state.ball_x <= 196
        and 187 <= ctx.state.ball_y <= 191
        and 195 <= ctx.state.self_paddle_y <= 199
        and 187 <= ctx.target_y <= 191
        and ctx.action == UP
    )


def trigger_high_down_score11_y205_final_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 1
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 8
        and 200 <= ctx.state.ball_x <= 204
        and 203 <= ctx.state.ball_y <= 207
        and 167 <= ctx.state.self_paddle_y <= 171
        and 194 <= ctx.target_y <= 196
        and ctx.action == DOWN
    )


def trigger_seed44_first_loss_down(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 0
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and 177 <= ctx.state.ball_x <= 181
        and 53 <= ctx.state.ball_y <= 57
        and 45 <= ctx.state.self_paddle_y <= 55
        and 65 <= ctx.target_y <= 70
        and ctx.action == DOWN
    )


def trigger_seed44_second_loss_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 1
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == 4
        and 185 <= ctx.state.ball_x <= 193
        and 136 <= ctx.state.ball_y <= 144
        and 155 <= ctx.state.self_paddle_y <= 175
        and 140 <= ctx.target_y <= 145
        and ctx.action == UP
    )


def trigger_seed44_third_loss_up(ctx: StepContext) -> bool:
    return (
        ctx.state.self_score == 0
        and ctx.state.opponent_score == 2
        and ctx.dx is not None
        and ctx.dx > 0
        and ctx.dy == -8
        and 189 <= ctx.state.ball_x <= 197
        and 159 <= ctx.state.ball_y <= 175
        and 175 <= ctx.state.self_paddle_y <= 195
        and 155 <= ctx.target_y <= 170
        and ctx.action == UP
    )


VARIANTS: dict[str, Variant] = {
    "baseline": Variant("baseline", None, None),
    "once_dy4_noop_to_down_h1": Variant("once_dy4_noop_to_down_h1", trigger_dy4_noop_prev_down, DOWN, 1),
    "once_dy4_noop_to_down_h3": Variant("once_dy4_noop_to_down_h3", trigger_dy4_noop_prev_down, DOWN, 3),
    "once_dy8_down_to_noop_h1": Variant("once_dy8_down_to_noop_h1", trigger_dy8_down_prev_up, NOOP, 1),
    "once_dy8_down_to_up_h1": Variant("once_dy8_down_to_up_h1", trigger_dy8_down_prev_up, UP, 1),
    "once_low_dy8_down_to_up_h1": Variant("once_low_dy8_down_to_up_h1", trigger_low_dy8_down_prev_up, UP, 1),
    "once_opening_low_dy8_down_to_up_h1": Variant(
        "once_opening_low_dy8_down_to_up_h1",
        trigger_opening_low_dy8_down_prev_up,
        UP,
        1,
    ),
    "once_dyneg8_noop_to_up_h1": Variant("once_dyneg8_noop_to_up_h1", trigger_dyneg8_noop_prev_up, UP, 1),
    "once_dyneg8_noop_to_down_h1": Variant("once_dyneg8_noop_to_down_h1", trigger_dyneg8_noop_prev_up, DOWN, 1),
    "repeat_low_serve_late_to_up_h1": Variant(
        "repeat_low_serve_late_to_up_h1",
        trigger_low_serve_late_up,
        UP,
        1,
        64,
    ),
    "repeat_low_serve_late_down_to_up_h1": Variant(
        "repeat_low_serve_late_down_to_up_h1",
        trigger_low_serve_late_down,
        UP,
        1,
        64,
    ),
    "repeat_low_serve_late_noop_to_up_h1": Variant(
        "repeat_low_serve_late_noop_to_up_h1",
        trigger_low_serve_late_noop,
        UP,
        1,
        64,
    ),
    "repeat_y86_noop_to_down_h1": Variant(
        "repeat_y86_noop_to_down_h1",
        trigger_y86_mid_down_noop,
        DOWN,
        1,
        64,
    ),
    "repeat_y86_up_to_noop_h1": Variant(
        "repeat_y86_up_to_noop_h1",
        trigger_y86_mid_down_up,
        NOOP,
        1,
        64,
    ),
    "repeat_y86_up_to_down_h1": Variant(
        "repeat_y86_up_to_down_h1",
        trigger_y86_mid_down_up,
        DOWN,
        1,
        64,
    ),
    "repeat_y86_up_prev_down_to_noop_h1": Variant(
        "repeat_y86_up_prev_down_to_noop_h1",
        trigger_y86_mid_down_up_prev_down,
        NOOP,
        1,
        64,
    ),
    "once_score00_mid_down_y154_final_up_to_noop_h1": Variant(
        "once_score00_mid_down_y154_final_up_to_noop_h1",
        trigger_score00_mid_down_y154_final_up,
        NOOP,
        1,
        1,
    ),
    "once_score00_mid_down_y154_final_up_to_down_h1": Variant(
        "once_score00_mid_down_y154_final_up_to_down_h1",
        trigger_score00_mid_down_y154_final_up,
        DOWN,
        1,
        1,
    ),
    "repeat_y86_any_prev_up_to_down_h1": Variant(
        "repeat_y86_any_prev_up_to_down_h1",
        trigger_y86_mid_down_any_prev_up,
        DOWN,
        1,
        64,
    ),
    "repeat_y86_pre_up_to_noop_h1": Variant(
        "repeat_y86_pre_up_to_noop_h1",
        trigger_y86_pre_contact_up,
        NOOP,
        1,
        64,
    ),
    "repeat_y86_pre_up_to_down_h1": Variant(
        "repeat_y86_pre_up_to_down_h1",
        trigger_y86_pre_contact_up,
        DOWN,
        1,
        64,
    ),
    "repeat_y86_pre_noop_to_down_h1": Variant(
        "repeat_y86_pre_noop_to_down_h1",
        trigger_y86_pre_contact_noop,
        DOWN,
        1,
        64,
    ),
    "repeat_y86_late_up_below_target_to_noop_h1": Variant(
        "repeat_y86_late_up_below_target_to_noop_h1",
        trigger_y86_late_up_below_target,
        NOOP,
        1,
        64,
    ),
    "repeat_y86_late_up_below_target_to_down_h1": Variant(
        "repeat_y86_late_up_below_target_to_down_h1",
        trigger_y86_late_up_below_target,
        DOWN,
        1,
        64,
    ),
    "repeat_y86_final_down_to_noop_h1": Variant(
        "repeat_y86_final_down_to_noop_h1",
        trigger_y86_final_down,
        NOOP,
        1,
        64,
    ),
    "repeat_y86_final_down_to_up_h1": Variant(
        "repeat_y86_final_down_to_up_h1",
        trigger_y86_final_down,
        UP,
        1,
        64,
    ),
    "repeat_y86_setup_offset8_up_to_noop_h1": Variant(
        "repeat_y86_setup_offset8_up_to_noop_h1",
        trigger_y86_setup_offset8_up,
        NOOP,
        1,
        64,
    ),
    "repeat_y86_setup_offset8_up_to_down_h1": Variant(
        "repeat_y86_setup_offset8_up_to_down_h1",
        trigger_y86_setup_offset8_up,
        DOWN,
        1,
        64,
    ),
    "repeat_y86_setup_offset8_opp2_up_to_down_h1": Variant(
        "repeat_y86_setup_offset8_opp2_up_to_down_h1",
        trigger_y86_setup_offset8_up_opp2,
        DOWN,
        1,
        64,
    ),
    "once_y86_setup_offset8_tied22_up_to_down_h1": Variant(
        "once_y86_setup_offset8_tied22_up_to_down_h1",
        trigger_y86_setup_offset8_up_tied22,
        DOWN,
        1,
        1,
    ),
    "once_y86_setup_offset8_score20_up_to_noop_h1": Variant(
        "once_y86_setup_offset8_score20_up_to_noop_h1",
        trigger_y86_setup_offset8_up_score20,
        NOOP,
        1,
        1,
    ),
    "once_y86_setup_offset8_score20_up_to_down_h1": Variant(
        "once_y86_setup_offset8_score20_up_to_down_h1",
        trigger_y86_setup_offset8_up_score20,
        DOWN,
        1,
        1,
    ),
    "once_y86_score20_approach_x82_up_to_noop_h1": Variant(
        "once_y86_score20_approach_x82_up_to_noop_h1",
        trigger_y86_score20_approach_x82_up,
        NOOP,
        1,
        1,
    ),
    "once_y86_score20_approach_x82_up_to_down_h1": Variant(
        "once_y86_score20_approach_x82_up_to_down_h1",
        trigger_y86_score20_approach_x82_up,
        DOWN,
        1,
        1,
    ),
    "once_y86_score20_serve_initial_down_to_noop_h1": Variant(
        "once_y86_score20_serve_initial_down_to_noop_h1",
        trigger_y86_score20_serve_initial_down,
        NOOP,
        1,
        1,
    ),
    "once_y86_score20_serve_initial_down_to_up_h1": Variant(
        "once_y86_score20_serve_initial_down_to_up_h1",
        trigger_y86_score20_serve_initial_down,
        UP,
        1,
        1,
    ),
    "once_y86_score60_serve_initial_down_to_noop_h1": Variant(
        "once_y86_score60_serve_initial_down_to_noop_h1",
        trigger_y86_score60_serve_initial_down,
        NOOP,
        1,
        1,
    ),
    "once_y86_score60_serve_initial_down_to_up_h1": Variant(
        "once_y86_score60_serve_initial_down_to_up_h1",
        trigger_y86_score60_serve_initial_down,
        UP,
        1,
        1,
    ),
    "once_y86_score100_serve_initial_down_to_noop_h1": Variant(
        "once_y86_score100_serve_initial_down_to_noop_h1",
        trigger_y86_score100_serve_initial_down,
        NOOP,
        1,
        1,
    ),
    "once_y86_score100_serve_initial_down_to_up_h1": Variant(
        "once_y86_score100_serve_initial_down_to_up_h1",
        trigger_y86_score100_serve_initial_down,
        UP,
        1,
        1,
    ),
    "once_y86_score140_serve_initial_down_to_noop_h1": Variant(
        "once_y86_score140_serve_initial_down_to_noop_h1",
        trigger_y86_score140_serve_initial_down,
        NOOP,
        1,
        1,
    ),
    "once_y86_score140_serve_initial_down_to_up_h1": Variant(
        "once_y86_score140_serve_initial_down_to_up_h1",
        trigger_y86_score140_serve_initial_down,
        UP,
        1,
        1,
    ),
    "once_y86_setup_offset8_score42_up_to_down_h1": Variant(
        "once_y86_setup_offset8_score42_up_to_down_h1",
        trigger_y86_setup_offset8_up_score42,
        DOWN,
        1,
        1,
    ),
    "once_y86_setup_offset8_score62_up_to_down_h1": Variant(
        "once_y86_setup_offset8_score62_up_to_down_h1",
        trigger_y86_setup_offset8_up_score62,
        DOWN,
        1,
        1,
    ),
    "once_y86_setup_offset8_score62_early_step_up_to_down_h1": Variant(
        "once_y86_setup_offset8_score62_early_step_up_to_down_h1",
        trigger_y86_setup_offset8_up_score62_early_step,
        DOWN,
        1,
        1,
    ),
    "once_y86_setup_offset8_score82_up_to_down_h1": Variant(
        "once_y86_setup_offset8_score82_up_to_down_h1",
        trigger_y86_setup_offset8_up_score82,
        DOWN,
        1,
        1,
    ),
    "once_y86_setup_offset8_score82_early_step_up_to_down_h1": Variant(
        "once_y86_setup_offset8_score82_early_step_up_to_down_h1",
        trigger_y86_setup_offset8_up_score82_early_step,
        DOWN,
        1,
        1,
    ),
    "repeat_y86_setup_offset6_down_to_noop_h1": Variant(
        "repeat_y86_setup_offset6_down_to_noop_h1",
        trigger_y86_setup_offset6_down,
        NOOP,
        1,
        64,
    ),
    "repeat_y86_setup_offset6_down_to_up_h1": Variant(
        "repeat_y86_setup_offset6_down_to_up_h1",
        trigger_y86_setup_offset6_down,
        UP,
        1,
        64,
    ),
    "repeat_y86_bounce_x166_down_to_noop_h1": Variant(
        "repeat_y86_bounce_x166_down_to_noop_h1",
        trigger_y86_bounce_x166_down,
        NOOP,
        1,
        64,
    ),
    "repeat_y86_bounce_x166_down_to_up_h1": Variant(
        "repeat_y86_bounce_x166_down_to_up_h1",
        trigger_y86_bounce_x166_down,
        UP,
        1,
        64,
    ),
    "repeat_y86_bounce_score22_x166_down_to_up_h1": Variant(
        "repeat_y86_bounce_score22_x166_down_to_up_h1",
        trigger_y86_bounce_score22_x166_down,
        UP,
        1,
        64,
    ),
    "repeat_y86_bounce_not_score60_x166_down_to_up_h1": Variant(
        "repeat_y86_bounce_not_score60_x166_down_to_up_h1",
        trigger_y86_bounce_not_score60_x166_down,
        UP,
        1,
        64,
    ),
    "once_y86_bounce_score60_x166_down_to_noop_h1": Variant(
        "once_y86_bounce_score60_x166_down_to_noop_h1",
        trigger_y86_bounce_score60_x166_down,
        NOOP,
        1,
        1,
    ),
    "once_y86_bounce_score60_x166_down_to_up_h1": Variant(
        "once_y86_bounce_score60_x166_down_to_up_h1",
        trigger_y86_bounce_score60_x166_down,
        UP,
        1,
        1,
    ),
    "repeat_y86_bounce_after_score22_x166_down_to_up_h1": Variant(
        "repeat_y86_bounce_after_score22_x166_down_to_up_h1",
        trigger_y86_bounce_x166_down,
        UP,
        1,
        64,
    ),
    "repeat_high_down_pre_up_to_noop_h1": Variant(
        "repeat_high_down_pre_up_to_noop_h1",
        trigger_high_down_pre_contact_up,
        NOOP,
        1,
        64,
    ),
    "repeat_high_down_pre_up_to_down_h1": Variant(
        "repeat_high_down_pre_up_to_down_h1",
        trigger_high_down_pre_contact_up,
        DOWN,
        1,
        64,
    ),
    "repeat_high_down_pre_noop_to_down_h1": Variant(
        "repeat_high_down_pre_noop_to_down_h1",
        trigger_high_down_pre_contact_noop,
        DOWN,
        1,
        64,
    ),
    "repeat_high_down_y165_offset4_up_to_noop_h1": Variant(
        "repeat_high_down_y165_offset4_up_to_noop_h1",
        trigger_high_down_y165_offset4_up,
        NOOP,
        1,
        64,
    ),
    "repeat_high_down_y165_offset4_up_to_down_h1": Variant(
        "repeat_high_down_y165_offset4_up_to_down_h1",
        trigger_high_down_y165_offset4_up,
        DOWN,
        1,
        64,
    ),
    "repeat_high_down_y165_offset4_up_to_noop_h3": Variant(
        "repeat_high_down_y165_offset4_up_to_noop_h3",
        trigger_high_down_y165_offset4_up,
        NOOP,
        3,
        64,
    ),
    "repeat_high_down_y165_offset4_up_to_down_h3": Variant(
        "repeat_high_down_y165_offset4_up_to_down_h3",
        trigger_high_down_y165_offset4_up,
        DOWN,
        3,
        64,
    ),
    "repeat_high_down_y165_x166_down_to_noop_h1": Variant(
        "repeat_high_down_y165_x166_down_to_noop_h1",
        trigger_high_down_y165_x166_down,
        NOOP,
        1,
        64,
    ),
    "repeat_high_down_y165_x166_down_to_up_h1": Variant(
        "repeat_high_down_y165_x166_down_to_up_h1",
        trigger_high_down_y165_x166_down,
        UP,
        1,
        64,
    ),
    "repeat_high_down_y165_x150_up_to_noop_h1": Variant(
        "repeat_high_down_y165_x150_up_to_noop_h1",
        trigger_high_down_y165_x150_up,
        NOOP,
        1,
        64,
    ),
    "repeat_high_down_y165_bounce_x146_down_to_noop_h1": Variant(
        "repeat_high_down_y165_bounce_x146_down_to_noop_h1",
        trigger_high_down_y165_bounce_x146_down,
        NOOP,
        1,
        64,
    ),
    "repeat_high_down_y165_bounce_x146_down_to_up_h1": Variant(
        "repeat_high_down_y165_bounce_x146_down_to_up_h1",
        trigger_high_down_y165_bounce_x146_down,
        UP,
        1,
        64,
    ),
    "repeat_high_down_y165_bounce_score40_x146_down_to_noop_h1": Variant(
        "repeat_high_down_y165_bounce_score40_x146_down_to_noop_h1",
        trigger_high_down_y165_bounce_score40_x146_down,
        NOOP,
        1,
        64,
    ),
    "once_high_down_y165_bounce_score80_x146_down_to_noop_h1": Variant(
        "once_high_down_y165_bounce_score80_x146_down_to_noop_h1",
        trigger_high_down_y165_bounce_score80_x146_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_y165_bounce_score80_x146_down_to_up_h1": Variant(
        "once_high_down_y165_bounce_score80_x146_down_to_up_h1",
        trigger_high_down_y165_bounce_score80_x146_down,
        UP,
        1,
        1,
    ),
    "once_high_down_y165_bounce_score120_x146_down_to_noop_h1": Variant(
        "once_high_down_y165_bounce_score120_x146_down_to_noop_h1",
        trigger_high_down_y165_bounce_score120_x146_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_y165_bounce_score120_x146_down_to_up_h1": Variant(
        "once_high_down_y165_bounce_score120_x146_down_to_up_h1",
        trigger_high_down_y165_bounce_score120_x146_down,
        UP,
        1,
        1,
    ),
    "once_high_down_y165_bounce_score160_x146_down_to_noop_h1": Variant(
        "once_high_down_y165_bounce_score160_x146_down_to_noop_h1",
        trigger_high_down_y165_bounce_score160_x146_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_y165_bounce_score181_x146_down_to_noop_h1": Variant(
        "once_high_down_y165_bounce_score181_x146_down_to_noop_h1",
        trigger_high_down_y165_bounce_score181_x146_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_y165_bounce_score202_x146_down_to_noop_h1": Variant(
        "once_high_down_y165_bounce_score202_x146_down_to_noop_h1",
        trigger_high_down_y165_bounce_score202_x146_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_y165_initial_x166_down_to_up_h1": Variant(
        "once_high_down_y165_initial_x166_down_to_up_h1",
        trigger_high_down_y165_initial_x166_down,
        UP,
        1,
        1,
    ),
    "once_high_down_score00_y173_bounce_noop_to_down_h1": Variant(
        "once_high_down_score00_y173_bounce_noop_to_down_h1",
        trigger_high_down_score00_y173_bounce_noop,
        DOWN,
        1,
        1,
    ),
    "once_high_down_score00_y173_bounce_noop_to_up_h1": Variant(
        "once_high_down_score00_y173_bounce_noop_to_up_h1",
        trigger_high_down_score00_y173_bounce_noop,
        UP,
        1,
        1,
    ),
    "once_high_down_score00_y173_x148_up_to_noop_h1": Variant(
        "once_high_down_score00_y173_x148_up_to_noop_h1",
        trigger_high_down_score00_y173_x148_up,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score00_y173_x148_up_to_down_h1": Variant(
        "once_high_down_score00_y173_x148_up_to_down_h1",
        trigger_high_down_score00_y173_x148_up,
        DOWN,
        1,
        1,
    ),
    "once_high_down_score00_y173_x164_down_to_noop_h1": Variant(
        "once_high_down_score00_y173_x164_down_to_noop_h1",
        trigger_high_down_score00_y173_x164_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score00_y173_x164_down_to_up_h1": Variant(
        "once_high_down_score00_y173_x164_down_to_up_h1",
        trigger_high_down_score00_y173_x164_down,
        UP,
        1,
        1,
    ),
    "once_high_down_score00_y173_final_down_to_noop_h1": Variant(
        "once_high_down_score00_y173_final_down_to_noop_h1",
        trigger_high_down_score00_y173_final_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score00_y173_final_down_to_up_h1": Variant(
        "once_high_down_score00_y173_final_down_to_up_h1",
        trigger_high_down_score00_y173_final_down,
        UP,
        1,
        1,
    ),
    "once_high_down_score21_y171_x155_down_to_noop_h1": Variant(
        "once_high_down_score21_y171_x155_down_to_noop_h1",
        trigger_high_down_score21_y171_x155_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score21_y171_x155_down_to_up_h1": Variant(
        "once_high_down_score21_y171_x155_down_to_up_h1",
        trigger_high_down_score21_y171_x155_down,
        UP,
        1,
        1,
    ),
    "once_high_down_score21_y171_setup_x135_up_to_noop_h1": Variant(
        "once_high_down_score21_y171_setup_x135_up_to_noop_h1",
        trigger_high_down_score21_y171_setup_x135_up,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score21_y171_setup_x135_up_to_down_h1": Variant(
        "once_high_down_score21_y171_setup_x135_up_to_down_h1",
        trigger_high_down_score21_y171_setup_x135_up,
        DOWN,
        1,
        1,
    ),
    "once_high_down_score21_y171_setup_x167_noop_to_up_h1": Variant(
        "once_high_down_score21_y171_setup_x167_noop_to_up_h1",
        trigger_high_down_score21_y171_setup_x167_noop,
        UP,
        1,
        1,
    ),
    "once_high_down_score21_y171_setup_x167_noop_to_down_h1": Variant(
        "once_high_down_score21_y171_setup_x167_noop_to_down_h1",
        trigger_high_down_score21_y171_setup_x167_noop,
        DOWN,
        1,
        1,
    ),
    "once_high_down_score21_y171_setup_x183_down_to_noop_h1": Variant(
        "once_high_down_score21_y171_setup_x183_down_to_noop_h1",
        trigger_high_down_score21_y171_setup_x183_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score21_y171_setup_x183_down_to_up_h1": Variant(
        "once_high_down_score21_y171_setup_x183_down_to_up_h1",
        trigger_high_down_score21_y171_setup_x183_down,
        UP,
        1,
        1,
    ),
    "once_high_down_score21_y171_x187_up_to_noop_h1": Variant(
        "once_high_down_score21_y171_x187_up_to_noop_h1",
        trigger_high_down_score21_y171_x187_up,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score21_y171_x187_up_to_down_h1": Variant(
        "once_high_down_score21_y171_x187_up_to_down_h1",
        trigger_high_down_score21_y171_x187_up,
        DOWN,
        1,
        1,
    ),
    "once_high_down_score21_y171_final_down_to_noop_h1": Variant(
        "once_high_down_score21_y171_final_down_to_noop_h1",
        trigger_high_down_score21_y171_final_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score21_y171_final_down_to_up_h1": Variant(
        "once_high_down_score21_y171_final_down_to_up_h1",
        trigger_high_down_score21_y171_final_down,
        UP,
        1,
        1,
    ),
    "once_high_down_score01_y203_x151_down_to_noop_h1": Variant(
        "once_high_down_score01_y203_x151_down_to_noop_h1",
        trigger_high_down_score01_y203_x151_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score01_y203_x151_down_to_up_h1": Variant(
        "once_high_down_score01_y203_x151_down_to_up_h1",
        trigger_high_down_score01_y203_x151_down,
        UP,
        1,
        1,
    ),
    "once_high_down_score01_y203_x167_down_to_noop_h1": Variant(
        "once_high_down_score01_y203_x167_down_to_noop_h1",
        trigger_high_down_score01_y203_x167_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score01_y203_x167_down_to_up_h1": Variant(
        "once_high_down_score01_y203_x167_down_to_up_h1",
        trigger_high_down_score01_y203_x167_down,
        UP,
        1,
        1,
    ),
    "once_high_down_score01_y203_final_up_to_noop_h1": Variant(
        "once_high_down_score01_y203_final_up_to_noop_h1",
        trigger_high_down_score01_y203_final_up,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score01_y203_final_up_to_down_h1": Variant(
        "once_high_down_score01_y203_final_up_to_down_h1",
        trigger_high_down_score01_y203_final_up,
        DOWN,
        1,
        1,
    ),
    "once_chain2_score00_initial_down_to_noop_h1": Variant(
        "once_chain2_score00_initial_down_to_noop_h1",
        trigger_chain2_score00_initial_down,
        NOOP,
        1,
        1,
    ),
    "once_chain2_score00_initial_down_to_up_h1": Variant(
        "once_chain2_score00_initial_down_to_up_h1",
        trigger_chain2_score00_initial_down,
        UP,
        1,
        1,
    ),
    "once_chain3_score20_initial_down_to_noop_h1": Variant(
        "once_chain3_score20_initial_down_to_noop_h1",
        trigger_chain3_score20_initial_down,
        NOOP,
        1,
        1,
    ),
    "once_chain3_score20_initial_down_to_noop_h2": Variant(
        "once_chain3_score20_initial_down_to_noop_h2",
        trigger_chain3_score20_initial_down,
        NOOP,
        2,
        1,
    ),
    "once_chain3_score20_initial_down_to_noop_h3": Variant(
        "once_chain3_score20_initial_down_to_noop_h3",
        trigger_chain3_score20_initial_down,
        NOOP,
        3,
        1,
    ),
    "once_chain3_score20_initial_down_to_up_h1": Variant(
        "once_chain3_score20_initial_down_to_up_h1",
        trigger_chain3_score20_initial_down,
        UP,
        1,
        1,
    ),
    "once_chain3_score20_initial_down_to_up_h2": Variant(
        "once_chain3_score20_initial_down_to_up_h2",
        trigger_chain3_score20_initial_down,
        UP,
        2,
        1,
    ),
    "once_chain3_score20_initial_down_to_up_h3": Variant(
        "once_chain3_score20_initial_down_to_up_h3",
        trigger_chain3_score20_initial_down,
        UP,
        3,
        1,
    ),
    "once_chain8_score11_initial_down_to_noop_h1": Variant(
        "once_chain8_score11_initial_down_to_noop_h1",
        trigger_chain8_score11_initial_down,
        NOOP,
        1,
        1,
    ),
    "once_chain8_score11_initial_down_to_noop_h2": Variant(
        "once_chain8_score11_initial_down_to_noop_h2",
        trigger_chain8_score11_initial_down,
        NOOP,
        2,
        1,
    ),
    "once_chain8_score11_initial_down_to_noop_h3": Variant(
        "once_chain8_score11_initial_down_to_noop_h3",
        trigger_chain8_score11_initial_down,
        NOOP,
        3,
        1,
    ),
    "once_chain8_score11_initial_down_to_up_h1": Variant(
        "once_chain8_score11_initial_down_to_up_h1",
        trigger_chain8_score11_initial_down,
        UP,
        1,
        1,
    ),
    "once_chain8_score11_initial_down_to_up_h2": Variant(
        "once_chain8_score11_initial_down_to_up_h2",
        trigger_chain8_score11_initial_down,
        UP,
        2,
        1,
    ),
    "once_chain8_score11_initial_down_to_up_h3": Variant(
        "once_chain8_score11_initial_down_to_up_h3",
        trigger_chain8_score11_initial_down,
        UP,
        3,
        1,
    ),
    "once_chain8_score11_phase_x106_noop_to_up_h1": Variant(
        "once_chain8_score11_phase_x106_noop_to_up_h1",
        trigger_chain8_score11_phase_x106_noop,
        UP,
        1,
        1,
    ),
    "once_chain8_score11_phase_x106_noop_to_down_h1": Variant(
        "once_chain8_score11_phase_x106_noop_to_down_h1",
        trigger_chain8_score11_phase_x106_noop,
        DOWN,
        1,
        1,
    ),
    "once_chain8_score11_inactive_pad85_down_to_noop_h1": Variant(
        "once_chain8_score11_inactive_pad85_down_to_noop_h1",
        trigger_chain8_score11_inactive_pad85_down,
        NOOP,
        1,
        1,
    ),
    "once_chain8_score11_inactive_pad85_down_to_noop_h2": Variant(
        "once_chain8_score11_inactive_pad85_down_to_noop_h2",
        trigger_chain8_score11_inactive_pad85_down,
        NOOP,
        2,
        1,
    ),
    "once_chain8_score11_inactive_pad85_down_to_noop_h3": Variant(
        "once_chain8_score11_inactive_pad85_down_to_noop_h3",
        trigger_chain8_score11_inactive_pad85_down,
        NOOP,
        3,
        1,
    ),
    "once_chain8_score11_inactive_pad85_down_to_up_h1": Variant(
        "once_chain8_score11_inactive_pad85_down_to_up_h1",
        trigger_chain8_score11_inactive_pad85_down,
        UP,
        1,
        1,
    ),
    "once_chain8_score11_inactive_pad85_down_to_up_h2": Variant(
        "once_chain8_score11_inactive_pad85_down_to_up_h2",
        trigger_chain8_score11_inactive_pad85_down,
        UP,
        2,
        1,
    ),
    "once_chain8_score11_inactive_pad85_down_to_up_h3": Variant(
        "once_chain8_score11_inactive_pad85_down_to_up_h3",
        trigger_chain8_score11_inactive_pad85_down,
        UP,
        3,
        1,
    ),
    "once_chain8_prev_terminal_y63_up_to_noop_h1": Variant(
        "once_chain8_prev_terminal_y63_up_to_noop_h1",
        trigger_chain8_prev_terminal_y63_up,
        NOOP,
        1,
        1,
    ),
    "once_chain8_prev_terminal_y63_up_to_noop_h2": Variant(
        "once_chain8_prev_terminal_y63_up_to_noop_h2",
        trigger_chain8_prev_terminal_y63_up,
        NOOP,
        2,
        1,
    ),
    "once_chain8_prev_terminal_y63_up_to_down_h1": Variant(
        "once_chain8_prev_terminal_y63_up_to_down_h1",
        trigger_chain8_prev_terminal_y63_up,
        DOWN,
        1,
        1,
    ),
    "once_chain8_prev_terminal_y63_up_to_down_h2": Variant(
        "once_chain8_prev_terminal_y63_up_to_down_h2",
        trigger_chain8_prev_terminal_y63_up,
        DOWN,
        2,
        1,
    ),
    "repeat_mid_up_y164_late_noop_to_down_h1": Variant(
        "repeat_mid_up_y164_late_noop_to_down_h1",
        trigger_mid_up_y164_late_noop,
        DOWN,
        1,
        64,
    ),
    "repeat_mid_up_y164_late_noop_to_up_h1": Variant(
        "repeat_mid_up_y164_late_noop_to_up_h1",
        trigger_mid_up_y164_late_noop,
        UP,
        1,
        64,
    ),
    "repeat_mid_up_y164_pre_up_to_noop_h1": Variant(
        "repeat_mid_up_y164_pre_up_to_noop_h1",
        trigger_mid_up_y164_pre_up,
        NOOP,
        1,
        64,
    ),
    "repeat_mid_up_y164_pre_up_to_down_h1": Variant(
        "repeat_mid_up_y164_pre_up_to_down_h1",
        trigger_mid_up_y164_pre_up,
        DOWN,
        1,
        64,
    ),
    "once_mid_up_y164_precontact_noop_to_up_h1": Variant(
        "once_mid_up_y164_precontact_noop_to_up_h1",
        trigger_mid_up_y164_precontact_noop,
        UP,
        1,
        1,
    ),
    "once_mid_up_y164_precontact_noop_to_up_h2": Variant(
        "once_mid_up_y164_precontact_noop_to_up_h2",
        trigger_mid_up_y164_precontact_noop,
        UP,
        2,
        1,
    ),
    "once_mid_up_y164_precontact_noop_to_down_h1": Variant(
        "once_mid_up_y164_precontact_noop_to_down_h1",
        trigger_mid_up_y164_precontact_noop,
        DOWN,
        1,
        1,
    ),
    "repeat_high_down_setup_up_to_noop_h1": Variant(
        "repeat_high_down_setup_up_to_noop_h1",
        trigger_high_down_setup_up,
        NOOP,
        1,
        64,
    ),
    "repeat_high_down_setup_up_to_down_h1": Variant(
        "repeat_high_down_setup_up_to_down_h1",
        trigger_high_down_setup_up,
        DOWN,
        1,
        64,
    ),
    "repeat_high_down_setup_down_to_noop_h1": Variant(
        "repeat_high_down_setup_down_to_noop_h1",
        trigger_high_down_setup_down,
        NOOP,
        1,
        64,
    ),
    "repeat_high_down_setup_noop_to_down_h1": Variant(
        "repeat_high_down_setup_noop_to_down_h1",
        trigger_high_down_setup_noop,
        DOWN,
        1,
        64,
    ),
    "repeat_late_low_loop_pre_up_to_noop_h1": Variant(
        "repeat_late_low_loop_pre_up_to_noop_h1",
        trigger_late_low_loop_pre_up,
        NOOP,
        1,
        64,
    ),
    "repeat_late_low_loop_pre_up_to_down_h1": Variant(
        "repeat_late_low_loop_pre_up_to_down_h1",
        trigger_late_low_loop_pre_up,
        DOWN,
        1,
        64,
    ),
    "repeat_late_low_loop_down_to_noop_h1": Variant(
        "repeat_late_low_loop_down_to_noop_h1",
        trigger_late_low_loop_down,
        NOOP,
        1,
        64,
    ),
    "repeat_seed33_low_up_pre_down_to_up_h1": Variant(
        "repeat_seed33_low_up_pre_down_to_up_h1",
        trigger_seed33_low_up_pre_down,
        UP,
        1,
        64,
    ),
    "repeat_seed33_low_up_pre_down_to_noop_h1": Variant(
        "repeat_seed33_low_up_pre_down_to_noop_h1",
        trigger_seed33_low_up_pre_down,
        NOOP,
        1,
        64,
    ),
    "repeat_seed33_low_up_late_up_to_noop_h1": Variant(
        "repeat_seed33_low_up_late_up_to_noop_h1",
        trigger_seed33_low_up_late_up,
        NOOP,
        1,
        64,
    ),
    "repeat_low_up_y111_pre_up_to_noop_h1": Variant(
        "repeat_low_up_y111_pre_up_to_noop_h1",
        trigger_low_up_y111_pre_up,
        NOOP,
        1,
        64,
    ),
    "repeat_low_up_y111_pre_up_to_down_h1": Variant(
        "repeat_low_up_y111_pre_up_to_down_h1",
        trigger_low_up_y111_pre_up,
        DOWN,
        1,
        64,
    ),
    "once_seed33_low_up_score01_down_to_noop_h1": Variant(
        "once_seed33_low_up_score01_down_to_noop_h1",
        trigger_seed33_low_up_score01_exact,
        NOOP,
        1,
        1,
    ),
    "once_low_up_score10_final_down_to_noop_h1": Variant(
        "once_low_up_score10_final_down_to_noop_h1",
        trigger_low_up_score10_final_down,
        NOOP,
        1,
        1,
    ),
    "once_low_up_score10_final_down_to_up_h1": Variant(
        "once_low_up_score10_final_down_to_up_h1",
        trigger_low_up_score10_final_down,
        UP,
        1,
        1,
    ),
    "once_low_up_score10_offset2_noop_to_down_h1": Variant(
        "once_low_up_score10_offset2_noop_to_down_h1",
        trigger_low_up_score10_offset2_noop,
        DOWN,
        1,
        1,
    ),
    "once_low_up_score00_y118_offset2_up_to_noop_h1": Variant(
        "once_low_up_score00_y118_offset2_up_to_noop_h1",
        trigger_low_up_score00_y118_offset2_up,
        NOOP,
        1,
        1,
    ),
    "once_low_up_score00_y118_offset2_up_to_down_h1": Variant(
        "once_low_up_score00_y118_offset2_up_to_down_h1",
        trigger_low_up_score00_y118_offset2_up,
        DOWN,
        1,
        1,
    ),
    "once_low_up_score00_y118_offset3_noop_to_down_h1": Variant(
        "once_low_up_score00_y118_offset3_noop_to_down_h1",
        trigger_low_up_score00_y118_offset3_noop,
        DOWN,
        1,
        1,
    ),
    "once_low_up_score12_y54_final_up_to_noop_h1": Variant(
        "once_low_up_score12_y54_final_up_to_noop_h1",
        trigger_low_up_score12_y54_final_up,
        NOOP,
        1,
        1,
    ),
    "once_low_up_score12_y54_final_up_to_down_h1": Variant(
        "once_low_up_score12_y54_final_up_to_down_h1",
        trigger_low_up_score12_y54_final_up,
        DOWN,
        1,
        1,
    ),
    "once_low_up_score12_y54_offset2_up_to_noop_h1": Variant(
        "once_low_up_score12_y54_offset2_up_to_noop_h1",
        trigger_low_up_score12_y54_offset2_up,
        NOOP,
        1,
        1,
    ),
    "once_low_up_score12_y54_offset2_up_to_down_h1": Variant(
        "once_low_up_score12_y54_offset2_up_to_down_h1",
        trigger_low_up_score12_y54_offset2_up,
        DOWN,
        1,
        1,
    ),
    "once_low_up_score12_y54_offset3_down_to_noop_h1": Variant(
        "once_low_up_score12_y54_offset3_down_to_noop_h1",
        trigger_low_up_score12_y54_offset3_down,
        NOOP,
        1,
        1,
    ),
    "once_low_up_score12_y54_offset3_down_to_up_h1": Variant(
        "once_low_up_score12_y54_offset3_down_to_up_h1",
        trigger_low_up_score12_y54_offset3_down,
        UP,
        1,
        1,
    ),
    "once_low_up_score12_y54_early_x150_down_to_noop_h1": Variant(
        "once_low_up_score12_y54_early_x150_down_to_noop_h1",
        trigger_low_up_score12_y54_early_x150_down,
        NOOP,
        1,
        1,
    ),
    "once_low_up_score12_y54_early_x150_down_to_up_h1": Variant(
        "once_low_up_score12_y54_early_x150_down_to_up_h1",
        trigger_low_up_score12_y54_early_x150_down,
        UP,
        1,
        1,
    ),
    "once_low_up_score12_y54_mid_x182_up_to_noop_h1": Variant(
        "once_low_up_score12_y54_mid_x182_up_to_noop_h1",
        trigger_low_up_score12_y54_mid_x182_up,
        NOOP,
        1,
        1,
    ),
    "once_low_up_score12_y54_mid_x182_up_to_down_h1": Variant(
        "once_low_up_score12_y54_mid_x182_up_to_down_h1",
        trigger_low_up_score12_y54_mid_x182_up,
        DOWN,
        1,
        1,
    ),
    "once_high_down_score11_y205_x186_noop_to_down_h1": Variant(
        "once_high_down_score11_y205_x186_noop_to_down_h1",
        trigger_high_down_score11_y205_x186_noop,
        DOWN,
        1,
        1,
    ),
    "once_high_down_score11_y205_x186_noop_to_up_h1": Variant(
        "once_high_down_score11_y205_x186_noop_to_up_h1",
        trigger_high_down_score11_y205_x186_noop,
        UP,
        1,
        1,
    ),
    "once_high_down_score11_y205_x194_up_to_noop_h1": Variant(
        "once_high_down_score11_y205_x194_up_to_noop_h1",
        trigger_high_down_score11_y205_x194_up,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score11_y205_x194_up_to_down_h1": Variant(
        "once_high_down_score11_y205_x194_up_to_down_h1",
        trigger_high_down_score11_y205_x194_up,
        DOWN,
        1,
        1,
    ),
    "once_high_down_score11_y205_final_down_to_noop_h1": Variant(
        "once_high_down_score11_y205_final_down_to_noop_h1",
        trigger_high_down_score11_y205_final_down,
        NOOP,
        1,
        1,
    ),
    "once_high_down_score11_y205_final_down_to_up_h1": Variant(
        "once_high_down_score11_y205_final_down_to_up_h1",
        trigger_high_down_score11_y205_final_down,
        UP,
        1,
        1,
    ),
    "repeat_seed44_first_down_to_noop_h1": Variant(
        "repeat_seed44_first_down_to_noop_h1",
        trigger_seed44_first_loss_down,
        NOOP,
        1,
        16,
    ),
    "repeat_seed44_first_down_to_up_h1": Variant(
        "repeat_seed44_first_down_to_up_h1",
        trigger_seed44_first_loss_down,
        UP,
        1,
        16,
    ),
    "repeat_seed44_second_up_to_noop_h1": Variant(
        "repeat_seed44_second_up_to_noop_h1",
        trigger_seed44_second_loss_up,
        NOOP,
        1,
        16,
    ),
    "repeat_seed44_second_up_to_down_h1": Variant(
        "repeat_seed44_second_up_to_down_h1",
        trigger_seed44_second_loss_up,
        DOWN,
        1,
        16,
    ),
    "repeat_seed44_third_up_to_noop_h1": Variant(
        "repeat_seed44_third_up_to_noop_h1",
        trigger_seed44_third_loss_up,
        NOOP,
        1,
        16,
    ),
    "repeat_seed44_third_up_to_down_h1": Variant(
        "repeat_seed44_third_up_to_down_h1",
        trigger_seed44_third_loss_up,
        DOWN,
        1,
        16,
    ),
}


def run_episode(
    seed: int,
    variant: Variant,
    max_steps: int,
    disable_opening_low_dy8_correction: bool,
) -> dict:
    env = envpool.make("Pong-v5", env_type="gym", num_envs=1, seed=seed)
    try:
        _obs, info = reset_env(env)
        policy = RamPolicy()
        policy.reset()
        policy.opening_low_dy8_correction_enabled = not disable_opening_low_dy8_correction
        score = 0.0
        previous_paddle_y: int | None = None
        override_steps = 0
        intervention_count = 0
        interventions = []
        y86_score22_bounce_seen = False

        for step in range(1, max_steps + 1):
            state = detect_state(info)
            previous_state = policy.previous_state
            action = policy.select_action(info)
            dx = None
            dy = None
            if previous_state is not None:
                dx = state.ball_x - previous_state.ball_x
                dy = state.ball_y - previous_state.ball_y
            previous_paddle_delta = None
            if previous_paddle_y is not None:
                previous_paddle_delta = state.self_paddle_y - previous_paddle_y

            ctx = StepContext(
                step=step,
                state=state,
                dx=dx,
                dy=dy,
                action=action,
                target_y=policy.last_target_y,
                mode=policy.last_mode,
                previous_paddle_delta=previous_paddle_delta,
            )
            if trigger_y86_bounce_score22_x166_down(ctx):
                y86_score22_bounce_seen = True
            trigger_matches = variant.trigger is not None and variant.trigger(ctx)
            if variant.name == "repeat_y86_bounce_after_score22_x166_down_to_up_h1":
                trigger_matches = y86_score22_bounce_seen and trigger_y86_bounce_x166_down(ctx)
            if (
                intervention_count < variant.max_interventions
                and variant.override_action is not None
                and trigger_matches
            ):
                intervention_count += 1
                override_steps = variant.horizon
                interventions.append(intervention_record(ctx, variant))

            executed_action = action
            if override_steps > 0 and variant.override_action is not None:
                executed_action = variant.override_action
                override_steps -= 1

            previous_paddle_y = state.self_paddle_y
            _obs, reward, done, info = step_env(env, executed_action)
            score += reward
            if done:
                return result_row(seed, variant.name, score, step, done, interventions)

        return result_row(seed, variant.name, score, max_steps, False, interventions)
    finally:
        env.close()


def intervention_record(ctx: StepContext, variant: Variant) -> dict:
    return {
        "step": ctx.step,
        "ball_x": ctx.state.ball_x,
        "ball_y": ctx.state.ball_y,
        "self_paddle_y": ctx.state.self_paddle_y,
        "self_score": ctx.state.self_score,
        "opponent_score": ctx.state.opponent_score,
        "dx": ctx.dx,
        "dy": ctx.dy,
        "baseline_action": ACTION_NAMES.get(ctx.action, str(ctx.action)),
        "override_action": ACTION_NAMES.get(variant.override_action, str(variant.override_action)),
        "horizon": variant.horizon,
        "target_y": ctx.target_y,
        "mode": ctx.mode,
        "previous_paddle_delta": ctx.previous_paddle_delta,
        "previous_paddle_delta_sign": sign(ctx.previous_paddle_delta),
    }


def result_row(
    seed: int,
    variant: str,
    score: float,
    steps: int,
    done: bool,
    interventions: list[dict],
) -> dict:
    return {
        "seed": seed,
        "variant": variant,
        "score": score,
        "steps": steps,
        "done": done,
        "intervention_count": len(interventions),
        "interventions": interventions,
    }


def parse_seeds(raw: str) -> list[int]:
    return [int(part.strip()) for part in raw.split(",") if part.strip()]


def parse_variants(raw: str) -> list[Variant]:
    variants = []
    for name in (part.strip() for part in raw.split(",")):
        if not name:
            continue
        if name not in VARIANTS:
            choices = ", ".join(sorted(VARIANTS))
            raise ValueError(f"unknown variant {name!r}; choices: {choices}")
        variants.append(VARIANTS[name])
    return variants


def write_jsonl(path: Path, rows: list[dict]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", encoding="utf-8") as handle:
        for row in rows:
            handle.write(json.dumps(row, separators=(",", ":")))
            handle.write("\n")


def main() -> None:
    parser = argparse.ArgumentParser(description="Probe one-shot counterfactual actions.")
    parser.add_argument("--seeds", default="0,1,2,3,4,5,6,7,8,9")
    parser.add_argument("--max-steps", type=int, default=30_000)
    parser.add_argument(
        "--variants",
        default=(
            "baseline,once_dy4_noop_to_down_h1,once_dy4_noop_to_down_h3,"
            "once_dy8_down_to_noop_h1,once_dy8_down_to_up_h1,"
            "once_dyneg8_noop_to_up_h1,once_dyneg8_noop_to_down_h1"
        ),
    )
    parser.add_argument("--jsonl", type=Path, default=Path("diagnostics/counterfactual_action_probe.jsonl"))
    parser.add_argument(
        "--disable-opening-low-dy8-correction",
        action="store_true",
        help="Run the imported RamPolicy as the pre-Round-6 baseline before variant interventions.",
    )
    args = parser.parse_args()

    seeds = parse_seeds(args.seeds)
    variants = parse_variants(args.variants)
    timestamp = datetime.now(timezone.utc).isoformat()
    rows = []
    total_variants = len(variants)
    for variant_index, variant in enumerate(variants, start=1):
        variant_rows = []
        for seed in seeds:
            row = run_episode(
                seed=seed,
                variant=variant,
                max_steps=args.max_steps,
                disable_opening_low_dy8_correction=args.disable_opening_low_dy8_correction,
            )
            row["timestamp"] = timestamp
            row["max_steps"] = args.max_steps
            row["opening_low_dy8_correction_enabled"] = not args.disable_opening_low_dy8_correction
            rows.append(row)
            variant_rows.append(row)
        scores = [row["score"] for row in variant_rows]
        interventions = sum(int(row["intervention_count"]) for row in variant_rows)
        print(
            f"progress {variant_index}/{total_variants}: {variant.name} "
            f"mean={mean(scores):.2f} min={min(scores):.0f} max={max(scores):.0f} interventions={interventions}",
            file=sys.stderr,
            flush=True,
        )

    write_jsonl(args.jsonl, rows)

    print(f"envpool: {envpool.__version__}")
    print(f"seeds: {','.join(str(seed) for seed in seeds)}")
    print(f"max_steps: {args.max_steps}")
    print(f"wrote: {args.jsonl}")
    print("variant,mean,min,max,scores,interventions")
    for variant in variants:
        variant_rows = [row for row in rows if row["variant"] == variant.name]
        scores = [row["score"] for row in variant_rows]
        interventions = sum(int(row["intervention_count"]) for row in variant_rows)
        score_text = ";".join(f"{row['seed']}:{row['score']:.0f}" for row in variant_rows)
        print(f"{variant.name},{mean(scores):.2f},{min(scores):.0f},{max(scores):.0f},{score_text},{interventions}")


if __name__ == "__main__":
    main()
