{
  "schema_version": "1",
  "id": "pybullet-raycast-skill",
  "name": "Ray intersection in headless procedural simulation",
  "summary": "Ray intersection in headless procedural simulation. Parameterized bounded JSON adapter; synthetic DIRECT simulation, no physical robot.",
  "capabilities": [
    "sensing",
    "raycast",
    "executable-json",
    "source-adaptation"
  ],
  "aliases": [
    "raycast",
    "Ray intersection in headless procedural simulation"
  ],
  "source": {
    "repository": "https://github.com/bulletphysics/bullet3",
    "commit": "2c204c49e56ed15ec5fcfa71d199ab6d6570b3f5",
    "paths": [
      "LICENSE.txt",
      "examples/pybullet/examples/batchRayCast.py"
    ],
    "archive_url": "https://codeload.github.com/bulletphysics/bullet3/tar.gz/2c204c49e56ed15ec5fcfa71d199ab6d6570b3f5",
    "archive_sha256": "f7c486f10b9f645d0577249d02855e076e8368babaa3d31ced6095e7162baee9",
    "archive_bytes": 139813655,
    "authors": [
      "Erwin Coumans and Bullet Physics contributors"
    ]
  },
  "license": {
    "spdx": "Zlib",
    "url": "https://github.com/bulletphysics/bullet3/blob/2c204c49e56ed15ec5fcfa71d199ab6d6570b3f5/LICENSE.txt",
    "verified": true,
    "scope": "Example source code. Robot models, textures, logs, and third-party assets retain their own upstream licenses."
  },
  "compatibility": [
    {
      "runtime": "pybullet",
      "runtime_version": "3.2.7",
      "robot": "synthetic-seven-axis-or-procedural-scene",
      "controller": "json-contract-v1",
      "observation": "explicit-json-input",
      "os": "linux",
      "architecture": "x86_64",
      "python": "3.10",
      "status": "hub_verified"
    }
  ],
  "observation": {
    "required": [
      "explicit_json_task_input"
    ],
    "privileged_state": true,
    "description": "All inputs and outputs follow interface v1. Simulation adapters query exact synthetic simulator state; no perception or physical sensor compatibility."
  },
  "requirements": {
    "python": ">=3.10,<3.11",
    "packages": [
      "numpy==1.26.4",
      "pybullet==3.2.7"
    ],
    "hardware": [
      "x86_64 CPU; no GPU or robot required"
    ],
    "rendering": "No display. Matplotlib Agg for numerical imports; PyBullet DIRECT for simulation.",
    "api_keys": [],
    "network": [
      "Pinned source archive once during preparation",
      "PyPI for declared dependency installation"
    ],
    "dependency_lock": "Runtime dependency versions are pinned for the supported Python 3.10 x86_64 CPU environments and checked against execution receipts. Platform-specific wheel hashes are not represented as a universal artifact lock; prepare captures pip freeze."
  },
  "invocation": {
    "entrypoint": "python run.py",
    "argv": [
      "{python}",
      "{recipe_dir}/run.py",
      "--source-dir",
      "{source_dir}",
      "--example"
    ],
    "inputs": {
      "box_center": [
        0,
        0,
        1
      ],
      "ray_from": [
        0,
        0,
        2
      ],
      "ray_to": [
        0,
        0,
        0
      ]
    },
    "outputs": {
      "format": "single JSON object on stdout",
      "payload_selector": "outputs",
      "schema": {
        "type": "object",
        "required": [
          "hit",
          "hit_fraction",
          "hit_xyz_m",
          "normal_xyz"
        ],
        "additionalProperties": false,
        "properties": {
          "hit": {
            "type": "boolean",
            "x-unit": "dimensionless",
            "x-frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
            "description": "hit; units=dimensionless; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin"
          },
          "hit_fraction": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "x-unit": "dimensionless",
            "x-frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
            "description": "hit_fraction; units=dimensionless; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin"
          },
          "hit_xyz_m": {
            "type": "array",
            "minItems": 3,
            "maxItems": 3,
            "items": {
              "type": "number",
              "minimum": -10,
              "maximum": 10
            },
            "x-unit": "m",
            "x-frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
            "x-order": [
              "x",
              "y",
              "z"
            ],
            "description": "hit_xyz_m; units=m; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z"
          },
          "normal_xyz": {
            "type": "array",
            "minItems": 3,
            "maxItems": 3,
            "items": {
              "type": "number",
              "minimum": -1,
              "maximum": 1
            },
            "x-unit": "dimensionless",
            "x-frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
            "x-order": [
              "x",
              "y",
              "z"
            ],
            "description": "normal_xyz; units=dimensionless; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z"
          }
        }
      }
    },
    "example": "After prepare, replace --example in next_command with --input /absolute/path/task.json; use --describe for the exact contract without execution."
  },
  "interface": {
    "version": "1",
    "kind": "sensing",
    "abstraction_level": 0,
    "execution_domain": "simulation",
    "language": "python",
    "input_transport": "json_file_or_inline",
    "output_transport": "json_stdout",
    "conventions": {
      "linear_unit": "m",
      "angular_unit": "rad",
      "time_unit": "s",
      "coordinate_frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
      "joint_order": "not_used",
      "quaternion_order": "not_used"
    },
    "input_schema": {
      "type": "object",
      "required": [
        "box_center",
        "ray_from",
        "ray_to"
      ],
      "additionalProperties": false,
      "properties": {
        "box_center": {
          "type": "array",
          "minItems": 3,
          "maxItems": 3,
          "items": {
            "type": "number",
            "minimum": -10,
            "maximum": 10
          },
          "x-unit": "m",
          "x-frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
          "x-order": [
            "x",
            "y",
            "z"
          ],
          "description": "box_center; units=m; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z"
        },
        "ray_from": {
          "type": "array",
          "minItems": 3,
          "maxItems": 3,
          "items": {
            "type": "number",
            "minimum": -10,
            "maximum": 10
          },
          "x-unit": "m",
          "x-frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
          "x-order": [
            "x",
            "y",
            "z"
          ],
          "description": "ray_from; units=m; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z"
        },
        "ray_to": {
          "type": "array",
          "minItems": 3,
          "maxItems": 3,
          "items": {
            "type": "number",
            "minimum": -10,
            "maximum": 10
          },
          "x-unit": "m",
          "x-frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
          "x-order": [
            "x",
            "y",
            "z"
          ],
          "description": "ray_to; units=m; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z"
        }
      }
    },
    "output_schema": {
      "type": "object",
      "required": [
        "hit",
        "hit_fraction",
        "hit_xyz_m",
        "normal_xyz"
      ],
      "additionalProperties": false,
      "properties": {
        "hit": {
          "type": "boolean",
          "x-unit": "dimensionless",
          "x-frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
          "description": "hit; units=dimensionless; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin"
        },
        "hit_fraction": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "x-unit": "dimensionless",
          "x-frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
          "description": "hit_fraction; units=dimensionless; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin"
        },
        "hit_xyz_m": {
          "type": "array",
          "minItems": 3,
          "maxItems": 3,
          "items": {
            "type": "number",
            "minimum": -10,
            "maximum": 10
          },
          "x-unit": "m",
          "x-frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
          "x-order": [
            "x",
            "y",
            "z"
          ],
          "description": "hit_xyz_m; units=m; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z"
        },
        "normal_xyz": {
          "type": "array",
          "minItems": 3,
          "maxItems": 3,
          "items": {
            "type": "number",
            "minimum": -1,
            "maximum": 1
          },
          "x-unit": "dimensionless",
          "x-frame": "right_handed_world_xyz; +z_up; procedural_arm_base=world_origin",
          "x-order": [
            "x",
            "y",
            "z"
          ],
          "description": "normal_xyz; units=dimensionless; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z"
        }
      }
    },
    "preconditions": [
      "PyBullet DIRECT CPU simulation with procedural shapes and a synthetic seven-axis arm where applicable; no downloaded models.",
      "This is an adaptation of the referenced API example, not verification of its original robot model or hardware.",
      "Simulation steps are fixed and bounded; force/torque order is Fx,Fy,Fz,Mx,My,Mz in the joint sensor frame."
    ],
    "postconditions": [
      "All declared checks must pass and all outputs must be finite.",
      "Success applies only to this operation and model, not higher-level task completion."
    ],
    "failure_codes": [
      "invalid_input_or_environment",
      "execution_failed",
      "task_failed"
    ],
    "composition": {
      "depends_on": [],
      "automatic_frame_conversion": false,
      "automatic_hardware_execution": false
    },
    "output_selector": "outputs",
    "origin_reference_ids": [
      "pybullet-batchraycast"
    ],
    "origin_relationship": "adapted_from; inspect declared model and inputs, not original robot compatibility"
  },
  "completion": {
    "success_condition": "Exit 0, status=task_complete, success=true, finite outputs satisfying interface.output_schema and all declared numerical/model checks. This is not a physical robot or general task-success claim.",
    "timeout_seconds": 30,
    "failure_modes": [
      "Invalid or oversized JSON",
      "Source checksum mismatch",
      "Unavailable pinned dependency",
      "No valid path or postcondition failure",
      "Execution exceeding the declared bounded algorithm budget"
    ],
    "stop_condition": "Caller must terminate the separate adapter process after 30 seconds; adapters bound iterations/scene steps and close simulation clients."
  },
  "preparation": {
    "recipe_path": "recipes/pybullet-raycast-skill",
    "requirements_file": "recipes/pybullet-raycast-skill/requirements.txt",
    "source_selection": "declared_paths",
    "install_argv": [
      "{python}",
      "-m",
      "pip",
      "install",
      "--only-binary=:all:",
      "-r",
      "{recipe_dir}/requirements.txt"
    ],
    "execution_separate": true,
    "files": {
      "run.py": "# Generated from tools/recipe_adapters and catalog/executable-conversions.json.\n# Hub adapter: MIT. Adapted upstream example retains its original license and authors.\n\"\"\"Source template for standalone, bounded JSON recipe adapters (MIT, Hub authors).\n\nThe generator embeds this template and a reviewed backend in each published\nrecipe. Importing this template does not import or execute upstream source.\n\"\"\"\nimport argparse\nimport contextlib\nimport hashlib\nimport importlib.util\nimport io\nimport json\nimport math\nimport os\nfrom pathlib import Path\nimport random\nimport sys\n\n\nclass ContractError(ValueError):\n    pass\n\n\ndef validate_data(value, schema, path='$'):\n    \"\"\"Supported, published JSON Schema subset; bool is never a number.\"\"\"\n    kind = schema.get('type')\n    checks = {'object': lambda: isinstance(value, dict), 'array': lambda: isinstance(value, list),\n              'number': lambda: type(value) in (int, float) and math.isfinite(value),\n              'integer': lambda: type(value) is int, 'string': lambda: isinstance(value, str),\n              'boolean': lambda: isinstance(value, bool), 'null': lambda: value is None}\n    if kind and (kind not in checks or not checks[kind]()):\n        raise ContractError(path + ': expected ' + str(kind))\n    if 'enum' in schema and value not in schema['enum']:\n        raise ContractError(path + ': unsupported value')\n    if type(value) in (int, float):\n        if not math.isfinite(value) or value < schema.get('minimum', value) or value > schema.get('maximum', value):\n            raise ContractError(path + ': outside numeric bounds')\n    if isinstance(value, dict):\n        if len(value) > 32: raise ContractError(path + ': too many keys')\n        for name in schema.get('required', []):\n            if name not in value: raise ContractError(path + '.' + name + ': required')\n        props = schema.get('properties', {})\n        for name, item in value.items():\n            if name not in props and schema.get('additionalProperties') is False:\n                raise ContractError(path + '.' + name + ': unknown field')\n            validate_data(item, props.get(name, {}), path + '.' + name)\n    elif isinstance(value, list):\n        if not schema.get('minItems', 0) <= len(value) <= schema.get('maxItems', 4096):\n            raise ContractError(path + ': invalid array length')\n        for i, item in enumerate(value): validate_data(item, schema.get('items', {}), path + '[' + str(i) + ']')\n    elif isinstance(value, str) and len(value) > schema.get('maxLength', 4096):\n        raise ContractError(path + ': string too long')\n\n\ndef verify_source(root, spec):\n    root = Path(root).resolve()\n    for name, expected in spec['source_hashes'].items():\n        target = root / name\n        if target.is_symlink() or not target.resolve().is_relative_to(root) or not target.is_file():\n            raise ContractError('Pinned source missing or outside source root: ' + name)\n        if hashlib.sha256(target.read_bytes()).hexdigest() != expected:\n            raise ContractError('Pinned source checksum changed: ' + name)\n    return root\n\n\ndef load_upstream(root, relative):\n    \"\"\"Only a generator-pinned path is accepted; never a path from task input.\"\"\"\n    target = root / relative\n    for directory in [root, root / 'PathPlanning', target.parent]:\n        sys.path.insert(0, str(directory))\n    module_spec = importlib.util.spec_from_file_location('rsh_upstream', target)\n    module = importlib.util.module_from_spec(module_spec)\n    module_spec.loader.exec_module(module)\n    if hasattr(module, 'show_animation'): module.show_animation = False\n    return module\n\n\ndef clean_json(value):\n    if hasattr(value, 'tolist'): return clean_json(value.tolist())\n    if isinstance(value, dict): return {k: clean_json(v) for k,v in value.items()}\n    if isinstance(value, (tuple, list)): return [clean_json(v) for v in value]\n    if hasattr(value, 'item'): return clean_json(value.item())\n    if isinstance(value, float) and not math.isfinite(value): raise ContractError('Non-finite output')\n    return value\n\n\ndef adapter_main(spec, backend):\n    parser = argparse.ArgumentParser(description=spec['name'])\n    parser.add_argument('--source-dir')\n    group = parser.add_mutually_exclusive_group(required=True)\n    group.add_argument('--input', type=Path, help='UTF-8 JSON file; no code or paths inside task input')\n    group.add_argument('--input-json', help='Inline JSON task input')\n    group.add_argument('--example', action='store_true', help='Run the declared example input')\n    group.add_argument('--describe', action='store_true', help='Read the contract without importing upstream code')\n    args = parser.parse_args()\n    try:\n        if args.describe:\n            print(json.dumps({'skill_id': spec['id'], 'interface': spec['interface'], 'example_input': spec['example']}, allow_nan=False))\n            return 0\n        if args.input:\n            with args.input.open('rb') as stream: raw=stream.read(65537)\n        else: raw=args.input_json.encode() if args.input_json else None\n        if raw is not None and len(raw) > 65536: raise ContractError('Input exceeds 64 KiB')\n        data = json.loads(raw) if raw is not None else spec['example']\n        validate_data(data, spec['interface']['input_schema'])\n        if not args.source_dir: raise ContractError('--source-dir is required for execution')\n        root = verify_source(args.source_dir, spec)\n        os.environ['MPLBACKEND'] = 'Agg'\n        os.environ.setdefault('OMP_NUM_THREADS', '1')\n        random.seed(data.get('seed', 0))\n        # Upstream diagnostic prints do not contaminate the machine response.\n        with contextlib.redirect_stdout(sys.stderr):\n            outputs, checks = backend(root, data, spec)\n        outputs, checks = clean_json(outputs), clean_json(checks)\n        validate_data(outputs, spec['interface']['output_schema'])\n        if not checks or not all(type(v) is bool for v in checks.values()): raise ContractError('Invalid postcondition report')\n        ok = all(checks.values())\n        print(json.dumps({'schema_version':'1','skill_id':spec['id'],'status':'task_complete' if ok else 'task_failed',\n            'success':ok,'outputs':outputs,'checks':checks,'execution_domain':spec['interface']['execution_domain'],\n            'source_commit':spec['source_commit'],'interface_version':'1','hardware_execution':False}, allow_nan=False, separators=(',',':')))\n        return 0 if ok else 1\n    except (ContractError, ValueError, OSError, ImportError) as error:\n        print(json.dumps({'schema_version':'1','skill_id':spec['id'],'status':'failed','success':False,\n            'reason_code':'invalid_input_or_environment','message':str(error),'hardware_execution':False}, allow_nan=False))\n        return 2\n    except Exception as error:\n        print(json.dumps({'schema_version':'1','skill_id':spec['id'],'status':'failed','success':False,\n            'reason_code':'execution_failed','message':str(error)[:1000],'hardware_execution':False}, allow_nan=False))\n        return 1\n\n\n\"\"\"Headless bounded adaptations of official PyBullet examples (Hub MIT).\n\nUses only procedural geometry. No model downloads, asset licenses, GUI, robot\nservers or hardware calls are involved. The seven-axis arm is synthetic.\n\"\"\"\n\n\ndef pybullet_backend(root, d, spec):\n    import numpy as np\n    import pybullet as p\n    from importlib.metadata import version\n    if version('pybullet')!='3.2.7': raise ContractError('Requires the tested pybullet==3.2.7 binding')\n    client=p.connect(p.DIRECT)\n    if client<0: raise ContractError('DIRECT simulation unavailable')\n    try:\n        p.setPhysicsEngineParameter(deterministicOverlappingPairs=1,numSolverIterations=100)\n        p.setTimeStep(1/240)\n        def box(position,mass=1.,half=.1):\n            shape=p.createCollisionShape(p.GEOM_BOX,halfExtents=[half]*3)\n            return p.createMultiBody(baseMass=mass,baseCollisionShapeIndex=shape,basePosition=position)\n        def arm():\n            shape=p.createCollisionShape(p.GEOM_SPHERE,radius=.035)\n            robot=p.createMultiBody(baseMass=0,basePosition=[0,0,0],\n                linkMasses=[1.]*7,linkCollisionShapeIndices=[shape]*7,linkVisualShapeIndices=[-1]*7,\n                linkPositions=[[0,0,.2]]*7,linkOrientations=[[0,0,0,1]]*7,\n                linkInertialFramePositions=[[0,0,0]]*7,linkInertialFrameOrientations=[[0,0,0,1]]*7,\n                linkParentIndices=list(range(7)),linkJointTypes=[p.JOINT_REVOLUTE]*7,\n                linkJointAxis=[[0,0,1],[0,1,0],[0,1,0],[1,0,0],[0,1,0],[1,0,0],[0,0,1]])\n            p.setJointMotorControlArray(robot,list(range(7)),p.VELOCITY_CONTROL,forces=[0.]*7)\n            return robot\n        op=spec['operation']\n        if op in ['ik','jacobian','inverse_dynamics','position_control','torque_pd','force_torque']:\n            robot=arm(); q=d['joint_angles']\n            for j,v in enumerate(q): p.resetJointState(robot,j,v)\n            if op=='ik':\n                target=d['target_xyz']\n                joints=p.calculateInverseKinematics(robot,6,target,maxNumIterations=1000,residualThreshold=1e-7)\n                for j,v in enumerate(joints): p.resetJointState(robot,j,v)\n                point=p.getLinkState(robot,6,computeForwardKinematics=1)[4]\n                error=math.dist(point,target)\n                return {'joint_angles_rad':joints,'end_effector_xyz_m':point,'position_error_m':error}, {'target_residual':error<.002}\n            if op=='jacobian':\n                linear,angular=p.calculateJacobian(robot,6,[0,0,0],q,[0.]*7,[0.]*7)\n                finite_difference=np.zeros((3,7)); eps=1e-5\n                for j in range(7):\n                    p.resetJointState(robot,j,q[j]+eps); plus=np.array(p.getLinkState(robot,6,computeForwardKinematics=1)[0])\n                    p.resetJointState(robot,j,q[j]-eps); minus=np.array(p.getLinkState(robot,6,computeForwardKinematics=1)[0])\n                    p.resetJointState(robot,j,q[j]); finite_difference[:,j]=(plus-minus)/(2*eps)\n                error=float(np.max(np.abs(np.array(linear)-finite_difference)))\n                return {'linear_jacobian':linear,'angular_jacobian':angular,'finite_difference_max_error':error}, {'matches_finite_difference':error<1e-5}\n            if op=='inverse_dynamics':\n                p.setGravity(0,0,-9.81)\n                accel=d['joint_acceleration']; gravity=np.array(p.calculateInverseDynamics(robot,q,[0.]*7,[0.]*7))\n                torque=np.array(p.calculateInverseDynamics(robot,q,[0.]*7,accel)); mass=np.array(p.calculateMassMatrix(robot,q))\n                residual=float(np.max(np.abs(torque-gravity-mass@np.array(accel))))\n                return {'torque_nm':torque,'gravity_torque_nm':gravity,'mass_matrix':mass,'dynamics_residual_nm':residual}, {\n                    'mass_matrix_symmetric':bool(np.allclose(mass,mass.T,atol=1e-9)), 'mass_matrix_positive_definite':bool(np.linalg.eigvalsh(mass).min()>0), 'inverse_dynamics_identity':residual<1e-7}\n            if op=='force_torque':\n                p.setGravity(0,0,-9.81); p.enableJointForceTorqueSensor(robot,3,enableSensor=1)\n                p.setJointMotorControlArray(robot,list(range(7)),p.POSITION_CONTROL,targetPositions=q,forces=[200]*7)\n                for _ in range(240): p.stepSimulation()\n                force=p.getJointState(robot,3)[2]\n                return {'reaction_force_torque':force}, {'sensor_has_six_components':len(force)==6,'finite_sensor':all(math.isfinite(v) for v in force),'load_observed':math.sqrt(sum(v*v for v in force))>1e-3}\n            target=np.array(d['target_joint_angles'])\n            if op=='position_control':\n                p.setJointMotorControlArray(robot,list(range(7)),p.POSITION_CONTROL,targetPositions=target.tolist(),forces=[200.]*7)\n                for _ in range(720): p.stepSimulation()\n            else:\n                for _ in range(1440):\n                    state=p.getJointStates(robot,list(range(7))); positions=np.array([s[0] for s in state]); velocities=np.array([s[1] for s in state])\n                    # The synthetic chain has widely different axis inertias;\n                    # mass-scaled PD uses inverse dynamics for those coordinates.\n                    acceleration=40*(target-positions)-12*velocities\n                    tau=p.calculateInverseDynamics(robot,positions.tolist(),velocities.tolist(),acceleration.tolist())\n                    p.setJointMotorControlArray(robot,list(range(7)),p.TORQUE_CONTROL,forces=tau)\n                    p.stepSimulation()\n            achieved=[s[0] for s in p.getJointStates(robot,list(range(7)))]; error=max(abs(a-b) for a,b in zip(achieved,target))\n            return {'joint_angles_rad':achieved,'maximum_joint_error_rad':error}, {'target_joint_residual':error<.02}\n        if op in ['fall','friction']:\n            p.setGravity(0,0,-9.81)\n            plane=p.createMultiBody(baseMass=0,baseCollisionShapeIndex=p.createCollisionShape(p.GEOM_PLANE))\n            body=box([0,0,d['height']],d['mass'])\n            p.changeDynamics(plane,-1,lateralFriction=d['friction'],restitution=0)\n            p.changeDynamics(body,-1,lateralFriction=d['friction'],restitution=0)\n            if op=='friction':\n                for _ in range(240): p.stepSimulation()\n                p.resetBaseVelocity(body,linearVelocity=[1.,0,0])\n            for _ in range(720): p.stepSimulation()\n            position=p.getBasePositionAndOrientation(body)[0]; velocity=p.getBaseVelocity(body)[0]; contacts=p.getContactPoints(body,plane)\n            checks={'settled_height':abs(position[2]-.1)<.005,'floor_contact':len(contacts)>0,'bounded_penetration':all(c[8]>-.005 for c in contacts)}\n            if op=='friction': checks['sliding_stopped']=math.hypot(velocity[0],velocity[1])<.02\n            return {'position_xyz_m':position,'velocity_xyz_m_s':velocity,'contact_count':len(contacts)},checks\n        if op=='raycast':\n            body=box(d['box_center'],mass=0)\n            result=p.rayTest(d['ray_from'],d['ray_to'])[0]\n            point=result[3]\n            return {'hit':result[0]==body,'hit_fraction':result[2],'hit_xyz_m':point,'normal_xyz':result[4]}, {\n                'expected_body_hit':result[0]==body,'fraction_in_segment':0<=result[2]<=1,\n                'intersection_on_box_surface':abs(max(abs(a-b) for a,b in zip(point,d['box_center']))-.1)<1e-6}\n        if op=='collision_filter':\n            a=box([0,0,1],mass=0); b=box([d['offset'],0,1])\n            p.performCollisionDetection(); before=len(p.getContactPoints(a,b))\n            p.setCollisionFilterPair(a,b,-1,-1,enableCollision=0); p.performCollisionDetection(); after=len(p.getContactPoints(a,b))\n            return {'contacts_enabled':before,'contacts_disabled':after}, {'overlap_detected':before>0,'filter_removed_contacts':after==0}\n        if op=='constraint':\n            p.setGravity(0,0,-9.81)\n            a=box([0,0,1],mass=0); b=box([0,0,1.5],mass=d['mass'])\n            p.createConstraint(a,-1,b,-1,p.JOINT_FIXED,[0,0,0],[0,0,.5],[0,0,0])\n            for _ in range(720): p.stepSimulation()\n            position=p.getBasePositionAndOrientation(b)[0]; error=math.dist(position,[0,0,1.5])\n            return {'child_xyz_m':position,'anchor_error_m':error}, {'fixed_constraint_holds':error<.005}\n        if op=='save_restore':\n            body=box(d['position']); state=p.saveState()\n            p.resetBasePositionAndOrientation(body,[3,2,1],[0,0,0,1]); p.restoreState(stateId=state)\n            position=p.getBasePositionAndOrientation(body)[0]; p.removeState(state)\n            return {'restored_xyz_m':position}, {'original_state_restored':math.dist(position,d['position'])<1e-10}\n        raise ContractError('Unknown reviewed simulation operation')\n    finally:\n        p.disconnect(client)\n\nSPEC = json.loads('{\"id\":\"pybullet-raycast-skill\",\"name\":\"Ray intersection in headless procedural simulation\",\"operation\":\"raycast\",\"source_path\":\"examples/pybullet/examples/batchRayCast.py\",\"source_commit\":\"2c204c49e56ed15ec5fcfa71d199ab6d6570b3f5\",\"source_hashes\":{\"LICENSE.txt\":\"885cd516a8420acb7c3e0b1a7119913b686495001ae564e3229740460e8cff59\",\"examples/pybullet/examples/batchRayCast.py\":\"d5331351922f6679d724a6c618827c60d127ae7dad14d239cf6064892cfc262e\"},\"interface\":{\"version\":\"1\",\"kind\":\"sensing\",\"abstraction_level\":0,\"execution_domain\":\"simulation\",\"language\":\"python\",\"input_transport\":\"json_file_or_inline\",\"output_transport\":\"json_stdout\",\"conventions\":{\"linear_unit\":\"m\",\"angular_unit\":\"rad\",\"time_unit\":\"s\",\"coordinate_frame\":\"right_handed_world_xyz; +z_up; procedural_arm_base=world_origin\",\"joint_order\":\"not_used\",\"quaternion_order\":\"not_used\"},\"input_schema\":{\"type\":\"object\",\"required\":[\"box_center\",\"ray_from\",\"ray_to\"],\"additionalProperties\":false,\"properties\":{\"box_center\":{\"type\":\"array\",\"minItems\":3,\"maxItems\":3,\"items\":{\"type\":\"number\",\"minimum\":-10,\"maximum\":10},\"x-unit\":\"m\",\"x-frame\":\"right_handed_world_xyz; +z_up; procedural_arm_base=world_origin\",\"x-order\":[\"x\",\"y\",\"z\"],\"description\":\"box_center; units=m; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z\"},\"ray_from\":{\"type\":\"array\",\"minItems\":3,\"maxItems\":3,\"items\":{\"type\":\"number\",\"minimum\":-10,\"maximum\":10},\"x-unit\":\"m\",\"x-frame\":\"right_handed_world_xyz; +z_up; procedural_arm_base=world_origin\",\"x-order\":[\"x\",\"y\",\"z\"],\"description\":\"ray_from; units=m; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z\"},\"ray_to\":{\"type\":\"array\",\"minItems\":3,\"maxItems\":3,\"items\":{\"type\":\"number\",\"minimum\":-10,\"maximum\":10},\"x-unit\":\"m\",\"x-frame\":\"right_handed_world_xyz; +z_up; procedural_arm_base=world_origin\",\"x-order\":[\"x\",\"y\",\"z\"],\"description\":\"ray_to; units=m; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z\"}}},\"output_schema\":{\"type\":\"object\",\"required\":[\"hit\",\"hit_fraction\",\"hit_xyz_m\",\"normal_xyz\"],\"additionalProperties\":false,\"properties\":{\"hit\":{\"type\":\"boolean\",\"x-unit\":\"dimensionless\",\"x-frame\":\"right_handed_world_xyz; +z_up; procedural_arm_base=world_origin\",\"description\":\"hit; units=dimensionless; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin\"},\"hit_fraction\":{\"type\":\"number\",\"minimum\":0,\"maximum\":1,\"x-unit\":\"dimensionless\",\"x-frame\":\"right_handed_world_xyz; +z_up; procedural_arm_base=world_origin\",\"description\":\"hit_fraction; units=dimensionless; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin\"},\"hit_xyz_m\":{\"type\":\"array\",\"minItems\":3,\"maxItems\":3,\"items\":{\"type\":\"number\",\"minimum\":-10,\"maximum\":10},\"x-unit\":\"m\",\"x-frame\":\"right_handed_world_xyz; +z_up; procedural_arm_base=world_origin\",\"x-order\":[\"x\",\"y\",\"z\"],\"description\":\"hit_xyz_m; units=m; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z\"},\"normal_xyz\":{\"type\":\"array\",\"minItems\":3,\"maxItems\":3,\"items\":{\"type\":\"number\",\"minimum\":-1,\"maximum\":1},\"x-unit\":\"dimensionless\",\"x-frame\":\"right_handed_world_xyz; +z_up; procedural_arm_base=world_origin\",\"x-order\":[\"x\",\"y\",\"z\"],\"description\":\"normal_xyz; units=dimensionless; frame=right_handed_world_xyz; +z_up; procedural_arm_base=world_origin; component order=x,y,z\"}}},\"preconditions\":[\"PyBullet DIRECT CPU simulation with procedural shapes and a synthetic seven-axis arm where applicable; no downloaded models.\",\"This is an adaptation of the referenced API example, not verification of its original robot model or hardware.\",\"Simulation steps are fixed and bounded; force/torque order is Fx,Fy,Fz,Mx,My,Mz in the joint sensor frame.\"],\"postconditions\":[\"All declared checks must pass and all outputs must be finite.\",\"Success applies only to this operation and model, not higher-level task completion.\"],\"failure_codes\":[\"invalid_input_or_environment\",\"execution_failed\",\"task_failed\"],\"composition\":{\"depends_on\":[],\"automatic_frame_conversion\":false,\"automatic_hardware_execution\":false},\"output_selector\":\"outputs\"},\"example\":{\"box_center\":[0,0,1],\"ray_from\":[0,0,2],\"ray_to\":[0,0,0]}}')\n\nif __name__ == \"__main__\":\n    raise SystemExit(adapter_main(SPEC, pybullet_backend))\n",
      "requirements.txt": "numpy==1.26.4\npybullet==3.2.7\n"
    }
  },
  "evidence": {
    "level": "task_complete",
    "verified_by": "Hub maintainer runtime harness",
    "date": "2026-10-19",
    "environment": {
      "scope": "simulation",
      "platforms": [
        "linux"
      ],
      "physical_robot": false
    },
    "commands": [
      "python tools/verify_executable_recipes.py --source-cache SOURCE_CACHE --output OUTPUT --backend pybullet"
    ],
    "result": "Three frozen positive cases passed against this exact adapter; see per-case receipts. This does not estimate general accuracy.",
    "artifacts": [
      "https://raw.githubusercontent.com/Superfast-Robotics/robot-skill-hub/39b06d7a6016edcea27e348e49ffd86f29ec1811/evidence/runtime/executable-conversions-v1/pybullet-raycast-skill.json"
    ],
    "runtime_verification": "task_complete",
    "contributor_reported": null,
    "hub_verification": {
      "runtime": "task_complete",
      "scope": "simulation",
      "case_count": 3,
      "source_unchanged": true
    }
  },
  "maintenance": {
    "record_version": "1.0.0",
    "last_checked": "2026-10-19",
    "upstream_status": "immutable_source_and_explicit_dependency_versions"
  },
  "contribution": {
    "allowed_paths": [
      "catalog/records/pybullet-raycast-skill.json",
      "docs/recipes/pybullet-raycast-skill.md",
      "recipes/pybullet-raycast-skill/run.py",
      "recipes/pybullet-raycast-skill/requirements.txt"
    ],
    "instructions": "Preserve upstream origin/license and interface semantics. Edit the finite manifest/templates and regenerate; record new exact-byte evidence for behavior changes."
  }
}
