{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.12.14"
    },
    "execution": {
      "method": "Python subprocess, exact code cell source",
      "deterministic_replay_passed": true
    },
    "runtime": {
      "packages": [],
      "local_assets": []
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "id": "question",
      "metadata": {},
      "source": [
        "# Why normalized clicks miss fixed-margin controls\n",
        "A viewport-relative point and a right-anchored control obey different transformations. Target width decides when that difference becomes a miss.\n",
        "\nPure geometry stress test; element positions are oracle inputs, not tested visual grounding.\n"
      ]
    },
    {
      "cell_type": "code",
      "id": "experiment",
      "metadata": {},
      "execution_count": 1,
      "source": [
        "\"\"\"Why normalized clicks miss fixed-margin controls\n",
        "Deterministic, standard-library experiment. RESULTS contains the full data.\n",
        "\"\"\"\n",
        "from math import hypot\n",
        "VIEWPORTS = [(800, 600), (1200, 900), (1000, 700), (600, 800), (1400, 1000)]\n",
        "layouts = [(kind, i) for kind in ('proportional', 'right_margin') for i in range(5)]\n",
        "\n",
        "def center(layout, viewport):\n",
        "    kind, i = layout\n",
        "    width, height = viewport\n",
        "    x = width * (0.2 + 0.1 * i) if kind == 'proportional' else width - 50 - 5 * i\n",
        "    return x, height / 2\n",
        "\n",
        "for kind in ('proportional', 'right_margin'):\n",
        "    for policy in ('pixel', 'normalized', 'element'):\n",
        "        hits, errors = 0, []\n",
        "        for layout in [x for x in layouts if x[0] == kind]:\n",
        "            old = center(layout, VIEWPORTS[0])\n",
        "            for viewport in VIEWPORTS:\n",
        "                target = center(layout, viewport)\n",
        "                if policy == 'pixel':\n",
        "                    click = old\n",
        "                elif policy == 'normalized':\n",
        "                    click = old[0] * viewport[0] / 800, old[1] * viewport[1] / 600\n",
        "                else:\n",
        "                    click = target  # Oracle current element center, not learned grounding.\n",
        "                dx, dy = click[0] - target[0], click[1] - target[1]\n",
        "                hits += abs(dx) <= 10 and abs(dy) <= 10\n",
        "                errors.append(hypot(dx, dy))\n",
        "        print(kind, policy, f'hits={hits}/25', f'mean_error={sum(errors)/25:.1f}px')\n",
        "assert center(('right_margin', 0), (1200, 900))[0] == 1150\n",
        "\n",
        "\n",
        "rows = []\n",
        "for layout in layouts:\n",
        "    for width,height in VIEWPORTS:\n",
        "        target=center(layout,(width,height)); old=center(layout,VIEWPORTS[0])\n",
        "        for policy in ('pixel','normalized','element'):\n",
        "            click=old if policy=='pixel' else ((old[0]*width/800,old[1]*height/600) if policy=='normalized' else target)\n",
        "            dx,dy=click[0]-target[0],click[1]-target[1]\n",
        "            rows.append(dict(kind=layout[0],layout=layout[1],width=width,height=height,policy=policy,target=list(target),click=list(click),dx=dx,dy=dy,error=hypot(dx,dy),hit=abs(dx)<=10 and abs(dy)<=10))\n",
        "tolerance=[]\n",
        "for half_width in (5,10,20,40,80):\n",
        "    tolerance.append(dict(half_width=half_width,hits=sum(abs(r['dx'])<=half_width and abs(r['dy'])<=half_width for r in rows if r['kind']=='right_margin' and r['policy']=='normalized')))\n",
        "RESULTS=dict(cases=rows,target_tolerance=tolerance)\n",
        "print('Right-margin normalized hit counts by half-width:',tolerance)\n"
      ],
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "proportional pixel hits=5/25 mean_error=153.2px\n",
            "proportional normalized hits=25/25 mean_error=0.0px\n",
            "proportional element hits=25/25 mean_error=0.0px\n",
            "right_margin pixel hits=5/25 mean_error=297.9px\n",
            "right_margin normalized hits=5/25 mean_error=21.0px\n",
            "right_margin element hits=25/25 mean_error=0.0px\n",
            "Right-margin normalized hit counts by half-width: [{'half_width': 5, 'hits': 5}, {'half_width': 10, 'hits': 5}, {'half_width': 20, 'hits': 15}, {'half_width': 40, 'hits': 21}, {'half_width': 80, 'hits': 25}]\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "from pathlib import Path\n",
        "import base64\n",
        "print('__NOTEBOOK_IMAGE__svg+xml:' + base64.b64encode(Path('figure.svg').read_bytes()).decode())\n"
      ]
    }
  ]
}
