From 1f3f23e73bc7c32354838e8087cc27803ab4046f Mon Sep 17 00:00:00 2001 From: Galen Lynch Date: Mon, 1 Jun 2026 15:30:32 -0700 Subject: [PATCH 1/8] feat: operant grid stages with velocity-based abort source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The probability-grid stages now run operant (is_operant=True), so abandoning a wait forfeits the reward — making the long delay a real wait-or-abandon decision (it was free water on a schedule before). Adds OperantLogic.abort_velocity_threshold (cm/s) as an ADDITIONAL abort source alongside grace distance and leaving the site: an operant choice aborts if velocity exceeds the threshold OR displacement exceeds grace_distance_threshold OR the animal leaves the reward site. It catches slow-creepers who lick while drifting forward (e.g. 860900: velocity stays <15 cm/s while engaged, ramps to ~45-55 to leave) — a pure spatial grace clips their genuine licking. Set to 15 on both grid stages; shaping stages leave it None. Optional (default None), so prior trainer states/JSON deserialize unchanged. NOT YET ENFORCED ON THE RIG (Python/curriculum side only): enforcing the threshold requires adding a velocity-abort branch ALONGSIDE the grace/leave-site sources in InstantiateSite.bonsai, plus regenerating the core JSON schema + C# (vr-foraging regenerate, needs .NET). The core schema and Generated.cs are not regenerated here. Co-Authored-By: Claude Opus 4.8 --- schema/depletion.json | 24 +- schema/depletion_stops_offset.json | 24 +- schema/depletion_stops_rate.json | 24 +- schema/deterministic_reversals.json | 24 +- ...deterministic_reversals_reward_capped.json | 24 +- schema/single_site.json | 408 +++++++++++------- schema/template.json | 12 +- .../aind_behavior_vr_foraging/task_logic.py | 7 + .../tests/test_task_logic.py | 27 ++ .../single_site/helpers.py | 23 +- .../single_site/stages.py | 16 +- 11 files changed, 403 insertions(+), 210 deletions(-) diff --git a/schema/depletion.json b/schema/depletion.json index bd511d17..e8a27837 100644 --- a/schema/depletion.json +++ b/schema/depletion.json @@ -77,7 +77,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -402,7 +403,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -745,7 +747,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1051,7 +1054,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1263,7 +1267,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1553,7 +1558,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1765,7 +1771,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1977,7 +1984,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", diff --git a/schema/depletion_stops_offset.json b/schema/depletion_stops_offset.json index 8d032efd..00dd0228 100644 --- a/schema/depletion_stops_offset.json +++ b/schema/depletion_stops_offset.json @@ -77,7 +77,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -402,7 +403,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -745,7 +747,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1051,7 +1054,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1263,7 +1267,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1553,7 +1558,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1765,7 +1771,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1977,7 +1984,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", diff --git a/schema/depletion_stops_rate.json b/schema/depletion_stops_rate.json index b9ac5dc6..060675fe 100644 --- a/schema/depletion_stops_rate.json +++ b/schema/depletion_stops_rate.json @@ -77,7 +77,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -402,7 +403,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -745,7 +747,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1051,7 +1054,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1263,7 +1267,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1553,7 +1558,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1765,7 +1771,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1977,7 +1984,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", diff --git a/schema/deterministic_reversals.json b/schema/deterministic_reversals.json index 85bf6632..499dcaf0 100644 --- a/schema/deterministic_reversals.json +++ b/schema/deterministic_reversals.json @@ -77,7 +77,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -402,7 +403,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -745,7 +747,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1051,7 +1054,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", @@ -1267,7 +1271,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", @@ -1567,7 +1572,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", @@ -1784,7 +1790,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", @@ -2006,7 +2013,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", diff --git a/schema/deterministic_reversals_reward_capped.json b/schema/deterministic_reversals_reward_capped.json index a84302e2..dc5224c1 100644 --- a/schema/deterministic_reversals_reward_capped.json +++ b/schema/deterministic_reversals_reward_capped.json @@ -77,7 +77,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -402,7 +403,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -745,7 +747,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1051,7 +1054,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", @@ -1267,7 +1271,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", @@ -1587,7 +1592,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", @@ -1804,7 +1810,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", @@ -2046,7 +2053,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", diff --git a/schema/single_site.json b/schema/single_site.json index f26b56dc..8b3ab3e7 100644 --- a/schema/single_site.json +++ b/schema/single_site.json @@ -53,7 +53,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -199,7 +200,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -468,7 +470,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -614,7 +617,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -802,7 +806,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -948,7 +953,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -1200,7 +1206,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -1211,7 +1217,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -1353,7 +1360,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -1364,7 +1371,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -1506,7 +1514,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -1517,7 +1525,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -1709,7 +1718,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -1720,7 +1729,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -1862,7 +1872,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -1873,7 +1883,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -2015,7 +2026,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -2026,7 +2037,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -2218,7 +2230,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -2229,7 +2241,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -2371,7 +2384,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -2382,7 +2395,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -2524,7 +2538,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -2535,7 +2549,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -2727,7 +2742,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -2738,7 +2753,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -2880,7 +2896,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -2891,7 +2907,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -3033,7 +3050,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -3044,7 +3061,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -3236,7 +3254,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -3247,7 +3265,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -3389,7 +3408,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -3400,7 +3419,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -3542,7 +3562,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -3553,7 +3573,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -3745,7 +3766,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -3756,7 +3777,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -3898,7 +3920,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -3909,7 +3931,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -4051,7 +4074,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -4062,7 +4085,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -4254,7 +4278,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -4265,7 +4289,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -4407,7 +4432,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -4418,7 +4443,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -4560,7 +4586,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -4571,7 +4597,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -4763,7 +4790,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -4774,7 +4801,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -4916,7 +4944,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -4927,7 +4955,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -5069,7 +5098,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -5080,7 +5109,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -5272,7 +5302,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -5283,7 +5313,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -5425,7 +5456,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -5436,7 +5467,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -5578,7 +5610,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -5589,7 +5621,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -5781,7 +5814,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -5792,7 +5825,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -5934,7 +5968,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -5945,7 +5979,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -6087,7 +6122,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -6098,7 +6133,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -6290,7 +6326,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -6301,7 +6337,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -6443,7 +6480,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -6454,7 +6491,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -6596,7 +6634,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -6607,7 +6645,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -6799,7 +6838,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -6810,7 +6849,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -6952,7 +6992,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -6963,7 +7003,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -7105,7 +7146,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -7116,7 +7157,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -7308,7 +7350,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -7319,7 +7361,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -7461,7 +7504,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -7472,7 +7515,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -7614,7 +7658,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -7625,7 +7669,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -7879,7 +7924,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -7890,7 +7935,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -8032,7 +8078,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -8043,7 +8089,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -8185,7 +8232,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -8196,7 +8243,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -8388,7 +8436,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -8399,7 +8447,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -8541,7 +8590,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -8552,7 +8601,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -8694,7 +8744,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -8705,7 +8755,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -8897,7 +8948,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -8908,7 +8959,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -9050,7 +9102,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -9061,7 +9113,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -9203,7 +9256,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -9214,7 +9267,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -9406,7 +9460,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -9417,7 +9471,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -9559,7 +9614,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -9570,7 +9625,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -9712,7 +9768,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -9723,7 +9779,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -9915,7 +9972,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -9926,7 +9983,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -10068,7 +10126,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -10079,7 +10137,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -10221,7 +10280,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -10232,7 +10291,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -10424,7 +10484,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -10435,7 +10495,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -10577,7 +10638,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -10588,7 +10649,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -10730,7 +10792,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -10741,7 +10803,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -10933,7 +10996,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -10944,7 +11007,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -11086,7 +11150,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -11097,7 +11161,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -11239,7 +11304,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -11250,7 +11315,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -11442,7 +11508,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -11453,7 +11519,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -11595,7 +11662,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -11606,7 +11673,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -11748,7 +11816,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -11759,7 +11827,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -11951,7 +12020,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -11962,7 +12031,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -12104,7 +12174,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -12115,7 +12185,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -12257,7 +12328,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -12268,7 +12339,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -12460,7 +12532,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -12471,7 +12543,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -12613,7 +12686,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -12624,7 +12697,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -12766,7 +12840,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -12777,7 +12851,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -12969,7 +13044,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -12980,7 +13055,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -13122,7 +13198,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -13133,7 +13209,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -13275,7 +13352,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -13286,7 +13363,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -13478,7 +13556,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -13489,7 +13567,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -13631,7 +13710,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -13642,7 +13721,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -13784,7 +13864,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -13795,7 +13875,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -13987,7 +14068,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -13998,7 +14079,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -14140,7 +14222,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -14151,7 +14233,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", @@ -14293,7 +14376,7 @@ ], "reward_specification": { "operant_logic": { - "is_operant": false, + "is_operant": true, "stop_duration": { "family": "Scalar", "distribution_parameters": { @@ -14304,7 +14387,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 50.0, + "abort_velocity_threshold": 15.0 }, "delay": { "family": "Exponential", diff --git a/schema/template.json b/schema/template.json index 2a8a214a..182ebd9c 100644 --- a/schema/template.json +++ b/schema/template.json @@ -77,7 +77,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 1000000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", @@ -333,7 +334,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 1000000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", @@ -681,7 +683,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 1000000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", @@ -937,7 +940,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 1000000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Exponential", diff --git a/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py b/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py index 2a096dbb..ffc0e7f6 100644 --- a/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py +++ b/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py @@ -200,6 +200,13 @@ class OperantLogic(BaseModel): grace_distance_threshold: float = Field( default=10, ge=0, description="Virtual distance (cm) the animal must be within to not abort the current choice" ) + abort_velocity_threshold: Optional[float] = Field( + default=None, + ge=0, + description="Velocity (cm/s) above which an in-progress operant choice is aborted. This is an ADDITIONAL " + "abort source: the choice aborts if velocity exceeds this OR displacement exceeds grace_distance_threshold " + "OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply).", + ) # NOTE: in InstantiateSite.bonsai, add a velocity-abort branch ALONGSIDE the grace/leave-site sources (do not replace them). class _PatchUpdateFunction(BaseModel): diff --git a/src/packages/aind_behavior_vr_foraging/tests/test_task_logic.py b/src/packages/aind_behavior_vr_foraging/tests/test_task_logic.py index acb95b33..8273e071 100644 --- a/src/packages/aind_behavior_vr_foraging/tests/test_task_logic.py +++ b/src/packages/aind_behavior_vr_foraging/tests/test_task_logic.py @@ -8,6 +8,7 @@ from aind_behavior_vr_foraging.task_logic import ( AindVrForagingTaskLogic, + OperantLogic, VirtualSite, _odor_mixture_from_odor_specification, _OdorSpecification, @@ -150,5 +151,31 @@ def test_tasklogic_output_deserializes(self): self.assertIsInstance(logic, AindVrForagingTaskLogic) +class TestOperantAbortVelocityThreshold(unittest.TestCase): + """The optional velocity-based operant abort field added alongside grace distance.""" + + def test_default_is_none(self): + """Defaults to None so existing (grace-distance) behavior is preserved.""" + self.assertIsNone(OperantLogic().abort_velocity_threshold) + + def test_accepts_value(self): + self.assertEqual(OperantLogic(abort_velocity_threshold=15).abort_velocity_threshold, 15.0) + + def test_rejects_negative(self): + with self.assertRaises(ValidationError): + OperantLogic(abort_velocity_threshold=-1) + + def test_backwards_compatible_deserialization(self): + """OperantLogic JSON written before this field (no key) still loads, as None.""" + legacy = { + "is_operant": True, + "stop_duration": {"family": "Scalar", "distribution_parameters": {"family": "Scalar", "value": 1.0}}, + "time_to_collect_reward": 100000.0, + "grace_distance_threshold": 10.0, + } + loaded = OperantLogic.model_validate(legacy) + self.assertIsNone(loaded.abort_velocity_threshold) + + if __name__ == "__main__": unittest.main() diff --git a/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/helpers.py b/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/helpers.py index a9a50bc0..42831560 100644 --- a/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/helpers.py +++ b/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/helpers.py @@ -55,9 +55,25 @@ def make_patch( inter_patch_mean_length: float = 150, inter_patch_max_length: float = 500, delay: Optional[distributions.Distribution] = None, + is_operant: bool = False, + abort_velocity_threshold: Optional[float] = None, + grace_distance_threshold: float = 10, ) -> task_logic.Patch: """A single odor-marked reward site. One accept/reject decision per patch - (``OnChoice``/``OnRejection`` count 1); reward is non-baited.""" + (``OnChoice``/``OnRejection`` count 1); reward is non-baited. + + ``is_operant`` gates collection: when False (shaping stages) the reward is + dispensed on the delay schedule regardless of where the animal is; when True + (probability-grid stages) the animal must stay engaged and collect, so abandoning + mid-delay forfeits the reward. + + ``abort_velocity_threshold`` (cm/s) adds a velocity-based abort *alongside* the + spatial ``grace_distance_threshold`` and leaving-the-site: an operant choice aborts + if velocity exceeds it OR displacement exceeds grace OR the animal leaves the site. + It catches slow-creepers who lick while drifting forward (their velocity stays low + but they cover distance); None leaves only grace + leave-site active. When velocity + is the intended gate, raise ``grace_distance_threshold`` so the spatial source does + not also clip the creeper (leave-the-site stays as the spatial backstop).""" if delay is None: delay = task_logic.scalar_value(0.5) return task_logic.Patch( @@ -74,10 +90,11 @@ def make_patch( available=task_logic.scalar_value(999999), delay=delay, operant_logic=task_logic.OperantLogic( - is_operant=False, + is_operant=is_operant, stop_duration=stop_duration, time_to_collect_reward=100000, - grace_distance_threshold=10, + grace_distance_threshold=grace_distance_threshold, + abort_velocity_threshold=abort_velocity_threshold, ), ), patch_virtual_sites_generator=task_logic.PatchVirtualSitesGenerator( diff --git a/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py b/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py index d8887ffa..f0f4246c 100644 --- a/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py +++ b/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py @@ -143,7 +143,21 @@ def _probability_grid_blocks( ) -> list[task_logic.Block]: """The 13-block band: every grid (p_A, p_B) whose sum is allowed, plus the 5% no-reward distractor odor C (occupancy 0.475 / 0.475 / 0.05).""" - make_patch_kwargs = {**_POST_STOP_PATCH_KWARGS, "delay": delay} + # is_operant=True: on the grid stages, abandoning a wait forfeits the reward, so the + # long delay is a real wait-or-abandon decision. The shaping stages stay non-operant. + # abort_velocity_threshold=15 cm/s adds a velocity abort ALONGSIDE grace distance + + # leaving the site. It catches slow-creepers who lick while drifting forward (e.g. + # 860900: velocity stays <15 while engaged, ramps to ~45-55 to leave). + # grace_distance_threshold raised to 50 cm (a full reward-site length, vs the 10 cm + # default) so the spatial source does not also clip that creep -- here velocity and + # leaving the site do the work, with grace only a far backstop. + make_patch_kwargs = { + **_POST_STOP_PATCH_KWARGS, + "delay": delay, + "is_operant": True, + "abort_velocity_threshold": 15, + "grace_distance_threshold": 50, + } return [ helpers.make_block( p_rewards=(p_a, p_b, 0.0), From 7511f7b6cdf91536337504fc1014d1edcbf0e611 Mon Sep 17 00:00:00 2001 From: Galen Lynch Date: Mon, 1 Jun 2026 20:48:35 -0700 Subject: [PATCH 2/8] fix: regenerate replenishment_depletion_offset schema for abort_velocity_threshold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This curriculum schema was left stale on the branch — it embeds OperantLogic but was missing the new abort_velocity_threshold field that the other six curriculum schemas already carry. Regenerated so the branch is internally consistent and a rig-side regenerate.py only produces the core schema/C# diff. Co-Authored-By: Claude Opus 4.8 --- schema/replenishment_depletion_offset.json | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/schema/replenishment_depletion_offset.json b/schema/replenishment_depletion_offset.json index 130a79c0..258b4d5c 100644 --- a/schema/replenishment_depletion_offset.json +++ b/schema/replenishment_depletion_offset.json @@ -77,7 +77,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -402,7 +403,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -745,7 +747,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Normal", @@ -1051,7 +1054,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -1508,7 +1512,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", @@ -1845,7 +1850,8 @@ "scaling_parameters": null }, "time_to_collect_reward": 100000.0, - "grace_distance_threshold": 10.0 + "grace_distance_threshold": 10.0, + "abort_velocity_threshold": null }, "delay": { "family": "Scalar", From 13d2af911de45acfdfd8172c93f9ff3cc5e30846 Mon Sep 17 00:00:00 2001 From: Galen Lynch Date: Mon, 1 Jun 2026 20:49:44 -0700 Subject: [PATCH 3/8] chore: regenerate schema --- schema/aind_behavior_vr_foraging.json | 14 +++++++++++ .../AindBehaviorVrForaging.Generated.cs | 23 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/schema/aind_behavior_vr_foraging.json b/schema/aind_behavior_vr_foraging.json index 26908dd0..47bcd768 100644 --- a/schema/aind_behavior_vr_foraging.json +++ b/schema/aind_behavior_vr_foraging.json @@ -3280,6 +3280,20 @@ "minimum": 0, "title": "Grace Distance Threshold", "type": "number" + }, + "abort_velocity_threshold": { + "default": null, + "description": "Velocity (cm/s) above which an in-progress operant choice is aborted. This is an ADDITIONAL abort source: the choice aborts if velocity exceeds this OR displacement exceeds grace_distance_threshold OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply).", + "oneOf": [ + { + "minimum": 0, + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Abort Velocity Threshold" } }, "title": "OperantLogic", diff --git a/src/Extensions/AindBehaviorVrForaging.Generated.cs b/src/Extensions/AindBehaviorVrForaging.Generated.cs index 4f90fd56..1e2763c1 100644 --- a/src/Extensions/AindBehaviorVrForaging.Generated.cs +++ b/src/Extensions/AindBehaviorVrForaging.Generated.cs @@ -5190,6 +5190,8 @@ public partial class OperantLogic private double _graceDistanceThreshold; + private double? _abortVelocityThreshold; + public OperantLogic() { _isOperant = true; @@ -5204,6 +5206,7 @@ protected OperantLogic(OperantLogic other) _stopDuration = other._stopDuration; _timeToCollectReward = other._timeToCollectReward; _graceDistanceThreshold = other._graceDistanceThreshold; + _abortVelocityThreshold = other._abortVelocityThreshold; } /// @@ -5275,6 +5278,23 @@ public double GraceDistanceThreshold } } + /// + /// Velocity (cm/s) above which an in-progress operant choice is aborted. This is an ADDITIONAL abort source: the choice aborts if velocity exceeds this OR displacement exceeds grace_distance_threshold OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply). + /// + [Newtonsoft.Json.JsonPropertyAttribute("abort_velocity_threshold")] + [System.ComponentModel.DescriptionAttribute(@"Velocity (cm/s) above which an in-progress operant choice is aborted. This is an ADDITIONAL abort source: the choice aborts if velocity exceeds this OR displacement exceeds grace_distance_threshold OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply).")] + public double? AbortVelocityThreshold + { + get + { + return _abortVelocityThreshold; + } + set + { + _abortVelocityThreshold = value; + } + } + public System.IObservable Generate() { return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return(new OperantLogic(this))); @@ -5290,7 +5310,8 @@ protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) stringBuilder.Append("IsOperant = " + _isOperant + ", "); stringBuilder.Append("StopDuration = " + _stopDuration + ", "); stringBuilder.Append("TimeToCollectReward = " + _timeToCollectReward + ", "); - stringBuilder.Append("GraceDistanceThreshold = " + _graceDistanceThreshold); + stringBuilder.Append("GraceDistanceThreshold = " + _graceDistanceThreshold + ", "); + stringBuilder.Append("AbortVelocityThreshold = " + _abortVelocityThreshold); return true; } From 281925535e9176f05c787bf6c46bc556adc6f8bb Mon Sep 17 00:00:00 2001 From: Galen Lynch Date: Mon, 1 Jun 2026 21:37:30 -0700 Subject: [PATCH 4/8] feat: attempt to add operant control velocity --- src/Extensions/InstantiateSite.bonsai | 60 ++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/src/Extensions/InstantiateSite.bonsai b/src/Extensions/InstantiateSite.bonsai index c14e9152..5dba7fbe 100644 --- a/src/Extensions/InstantiateSite.bonsai +++ b/src/Extensions/InstantiateSite.bonsai @@ -401,6 +401,23 @@ true + + FilteredCurrentVelocity + + + ThisRewardControl + + + OperantLogic.AbortVelocityThreshold + + + + Value + + + + + @@ -419,12 +436,19 @@ - + - - + + + + + + + + + @@ -673,6 +697,23 @@ it.Item2 as EntryPosition) true + + FilteredCurrentVelocity + + + ThisRewardControl + + + OperantLogic.AbortVelocityThreshold + + + + Value + + + + + @@ -690,12 +731,19 @@ it.Item2 as EntryPosition) - + - - + + + + + + + + + From 834948e7ac0b376641588339b64328a7ec10132d Mon Sep 17 00:00:00 2001 From: Galen Lynch Date: Mon, 1 Jun 2026 21:47:12 -0700 Subject: [PATCH 5/8] chore: fix types in bonsai workflow --- src/Extensions/InstantiateSite.bonsai | 40 ++++++++++++++++----------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/Extensions/InstantiateSite.bonsai b/src/Extensions/InstantiateSite.bonsai index 5dba7fbe..b8a68508 100644 --- a/src/Extensions/InstantiateSite.bonsai +++ b/src/Extensions/InstantiateSite.bonsai @@ -404,6 +404,9 @@ FilteredCurrentVelocity + + Item1 + ThisRewardControl @@ -436,19 +439,20 @@ - + - - - + + + - - - - + + + + + @@ -700,6 +704,9 @@ it.Item2 as EntryPosition) FilteredCurrentVelocity + + Item1 + ThisRewardControl @@ -731,19 +738,20 @@ it.Item2 as EntryPosition) - + - - - + + + - - - - + + + + + From 17293d45b5f031a366400876ed7484b1d8eaa831 Mon Sep 17 00:00:00 2001 From: Galen Lynch Date: Tue, 2 Jun 2026 08:45:35 -0700 Subject: [PATCH 6/8] docs: document abort_velocity_threshold interaction with the stop threshold Per review feedback: the velocity abort is evaluated on the same filtered velocity signal as stop detection (IsStopped = velocity < StopVelocityThreshold), so the two gates partition one velocity axis in opposite directions. Document that abort_velocity_threshold must stay >= the operative StopVelocityThreshold (else a locked stop instantly aborts), and that because StopVelocityThreshold is shaped (60->8 via the GAIN updater) while the abort is a fixed absolute, the abort may only be enabled where the stop gate is already floored below it. Add the matching rationale where the grid stages set 15 against their static stop threshold of 8. Docstring only; schema/*.json and Generated.cs must be regenerated on the rig. Co-Authored-By: Claude Opus 4.8 --- .../src/aind_behavior_vr_foraging/task_logic.py | 16 ++++++++++++++-- .../single_site/stages.py | 5 +++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py b/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py index ffc0e7f6..21407bac 100644 --- a/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py +++ b/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py @@ -205,8 +205,20 @@ class OperantLogic(BaseModel): ge=0, description="Velocity (cm/s) above which an in-progress operant choice is aborted. This is an ADDITIONAL " "abort source: the choice aborts if velocity exceeds this OR displacement exceeds grace_distance_threshold " - "OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply).", - ) # NOTE: in InstantiateSite.bonsai, add a velocity-abort branch ALONGSIDE the grace/leave-site sources (do not replace them). + "OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply). " + "Interaction with the stop threshold: this is evaluated on the SAME filtered velocity signal as stop " + "detection, which locks a choice when velocity falls BELOW StopVelocityThreshold " + "(UpdaterTarget.STOP_VELOCITY_THRESHOLD, seeded from the position-control velocity_threshold and shaped " + "within/across sessions). The two gates partition the same velocity axis in opposite directions, so this " + "value must be >= the operative StopVelocityThreshold: if it is lower, the band " + "(abort_velocity_threshold, StopVelocityThreshold) lets the animal lock a stop while already too fast to " + "hold it, forfeiting every such choice. Because StopVelocityThreshold is dynamic (e.g. shaped 60 -> 8 cm/s " + "by a GAIN updater) whereas this is a fixed absolute, only enable the velocity abort on stages where the " + "stop threshold is already floored below it (e.g. a static stop threshold of 8 with this set to 15); never " + "pair a low fixed abort with an actively-shaped, still-high stop threshold.", + ) # Implemented in InstantiateSite.bonsai as a third input merged ALONGSIDE the grace-distance and leave-site + # sources (the CheckDistanceFromEntry groups under WaitDelay and WaitForLick), comparing + # FilteredCurrentVelocity.Item1 -- the same signal used for stop detection -- against this value. class _PatchUpdateFunction(BaseModel): diff --git a/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py b/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py index f0f4246c..8fa5bde1 100644 --- a/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py +++ b/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py @@ -151,6 +151,11 @@ def _probability_grid_blocks( # grace_distance_threshold raised to 50 cm (a full reward-site length, vs the 10 cm # default) so the spatial source does not also clip that creep -- here velocity and # leaving the site do the work, with grace only a far backstop. + # The 15 sits safely above this stage's stop velocity threshold (a static 8 -- the grid + # stages carry no STOP_VELOCITY_THRESHOLD updater, so the gate does not shape up into it). + # abort_velocity_threshold must stay >= the stop threshold or a locked stop would instantly + # abort; that is why the abort is off on learn_to_stop, where the gate is still shaping 60->8. + # See OperantLogic.abort_velocity_threshold for the full interaction. make_patch_kwargs = { **_POST_STOP_PATCH_KWARGS, "delay": delay, From f34fdd293fe3ac357f70765713dd25d4e8248795 Mon Sep 17 00:00:00 2001 From: Galen Lynch Date: Tue, 2 Jun 2026 08:47:35 -0700 Subject: [PATCH 7/8] chore: trim redundant comments on the operant-abort PR Per review feedback. Drop the InstantiateSite.bonsai location pointer after the abort_velocity_threshold field (its semantics live in the field description) and condense the 13-line inline block in _probability_grid_blocks to the stage-specific values plus the stop-threshold constraint; the parameter mechanics are already in make_patch's docstring and the field description. Co-Authored-By: Claude Opus 4.8 --- .../src/aind_behavior_vr_foraging/task_logic.py | 4 +--- .../single_site/stages.py | 16 +++------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py b/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py index 21407bac..5565d2dd 100644 --- a/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py +++ b/src/packages/aind_behavior_vr_foraging/src/aind_behavior_vr_foraging/task_logic.py @@ -216,9 +216,7 @@ class OperantLogic(BaseModel): "by a GAIN updater) whereas this is a fixed absolute, only enable the velocity abort on stages where the " "stop threshold is already floored below it (e.g. a static stop threshold of 8 with this set to 15); never " "pair a low fixed abort with an actively-shaped, still-high stop threshold.", - ) # Implemented in InstantiateSite.bonsai as a third input merged ALONGSIDE the grace-distance and leave-site - # sources (the CheckDistanceFromEntry groups under WaitDelay and WaitForLick), comparing - # FilteredCurrentVelocity.Item1 -- the same signal used for stop detection -- against this value. + ) class _PatchUpdateFunction(BaseModel): diff --git a/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py b/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py index 8fa5bde1..ee67bc95 100644 --- a/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py +++ b/src/packages/aind_behavior_vr_foraging_curricula/src/aind_behavior_vr_foraging_curricula/single_site/stages.py @@ -143,19 +143,9 @@ def _probability_grid_blocks( ) -> list[task_logic.Block]: """The 13-block band: every grid (p_A, p_B) whose sum is allowed, plus the 5% no-reward distractor odor C (occupancy 0.475 / 0.475 / 0.05).""" - # is_operant=True: on the grid stages, abandoning a wait forfeits the reward, so the - # long delay is a real wait-or-abandon decision. The shaping stages stay non-operant. - # abort_velocity_threshold=15 cm/s adds a velocity abort ALONGSIDE grace distance + - # leaving the site. It catches slow-creepers who lick while drifting forward (e.g. - # 860900: velocity stays <15 while engaged, ramps to ~45-55 to leave). - # grace_distance_threshold raised to 50 cm (a full reward-site length, vs the 10 cm - # default) so the spatial source does not also clip that creep -- here velocity and - # leaving the site do the work, with grace only a far backstop. - # The 15 sits safely above this stage's stop velocity threshold (a static 8 -- the grid - # stages carry no STOP_VELOCITY_THRESHOLD updater, so the gate does not shape up into it). - # abort_velocity_threshold must stay >= the stop threshold or a locked stop would instantly - # abort; that is why the abort is off on learn_to_stop, where the gate is still shaping 60->8. - # See OperantLogic.abort_velocity_threshold for the full interaction. + # Grid stages are operant with a 15 cm/s velocity abort and grace raised to 50 cm (see + # make_patch). 15 stays above this stage's static stop threshold of 8 (no + # STOP_VELOCITY_THRESHOLD updater here) as OperantLogic.abort_velocity_threshold requires. make_patch_kwargs = { **_POST_STOP_PATCH_KWARGS, "delay": delay, From 751c80ca3018cccb13a9bf903d9d5b0ab7fef23d Mon Sep 17 00:00:00 2001 From: Galen Lynch Date: Tue, 2 Jun 2026 10:17:03 -0700 Subject: [PATCH 8/8] chore: regenerate schema --- schema/aind_behavior_vr_foraging.json | 2 +- .../AindBehaviorVrForaging.Generated.cs | 4 +- src/Extensions/InstantiateSite.bonsai | 40 ++++++++++++------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/schema/aind_behavior_vr_foraging.json b/schema/aind_behavior_vr_foraging.json index 47bcd768..23e9398c 100644 --- a/schema/aind_behavior_vr_foraging.json +++ b/schema/aind_behavior_vr_foraging.json @@ -3283,7 +3283,7 @@ }, "abort_velocity_threshold": { "default": null, - "description": "Velocity (cm/s) above which an in-progress operant choice is aborted. This is an ADDITIONAL abort source: the choice aborts if velocity exceeds this OR displacement exceeds grace_distance_threshold OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply).", + "description": "Velocity (cm/s) above which an in-progress operant choice is aborted. This is an ADDITIONAL abort source: the choice aborts if velocity exceeds this OR displacement exceeds grace_distance_threshold OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply). Interaction with the stop threshold: this is evaluated on the SAME filtered velocity signal as stop detection, which locks a choice when velocity falls BELOW StopVelocityThreshold (UpdaterTarget.STOP_VELOCITY_THRESHOLD, seeded from the position-control velocity_threshold and shaped within/across sessions). The two gates partition the same velocity axis in opposite directions, so this value must be >= the operative StopVelocityThreshold: if it is lower, the band (abort_velocity_threshold, StopVelocityThreshold) lets the animal lock a stop while already too fast to hold it, forfeiting every such choice. Because StopVelocityThreshold is dynamic (e.g. shaped 60 -> 8 cm/s by a GAIN updater) whereas this is a fixed absolute, only enable the velocity abort on stages where the stop threshold is already floored below it (e.g. a static stop threshold of 8 with this set to 15); never pair a low fixed abort with an actively-shaped, still-high stop threshold.", "oneOf": [ { "minimum": 0, diff --git a/src/Extensions/AindBehaviorVrForaging.Generated.cs b/src/Extensions/AindBehaviorVrForaging.Generated.cs index 1e2763c1..36efb119 100644 --- a/src/Extensions/AindBehaviorVrForaging.Generated.cs +++ b/src/Extensions/AindBehaviorVrForaging.Generated.cs @@ -5279,10 +5279,10 @@ public double GraceDistanceThreshold } /// - /// Velocity (cm/s) above which an in-progress operant choice is aborted. This is an ADDITIONAL abort source: the choice aborts if velocity exceeds this OR displacement exceeds grace_distance_threshold OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply). + /// Velocity (cm/s) above which an in-progress operant choice is aborted. This is an ADDITIONAL abort source: the choice aborts if velocity exceeds this OR displacement exceeds grace_distance_threshold OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply). Interaction with the stop threshold: this is evaluated on the SAME filtered velocity signal as stop detection, which locks a choice when velocity falls BELOW StopVelocityThreshold (UpdaterTarget.STOP_VELOCITY_THRESHOLD, seeded from the position-control velocity_threshold and shaped within/across sessions). The two gates partition the same velocity axis in opposite directions, so this value must be >= the operative StopVelocityThreshold: if it is lower, the band (abort_velocity_threshold, StopVelocityThreshold) lets the animal lock a stop while already too fast to hold it, forfeiting every such choice. Because StopVelocityThreshold is dynamic (e.g. shaped 60 -> 8 cm/s by a GAIN updater) whereas this is a fixed absolute, only enable the velocity abort on stages where the stop threshold is already floored below it (e.g. a static stop threshold of 8 with this set to 15); never pair a low fixed abort with an actively-shaped, still-high stop threshold. /// [Newtonsoft.Json.JsonPropertyAttribute("abort_velocity_threshold")] - [System.ComponentModel.DescriptionAttribute(@"Velocity (cm/s) above which an in-progress operant choice is aborted. This is an ADDITIONAL abort source: the choice aborts if velocity exceeds this OR displacement exceeds grace_distance_threshold OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply).")] + [System.ComponentModel.DescriptionAttribute(@"Velocity (cm/s) above which an in-progress operant choice is aborted. This is an ADDITIONAL abort source: the choice aborts if velocity exceeds this OR displacement exceeds grace_distance_threshold OR the animal leaves the reward site. None disables only the velocity source (grace + leave-site still apply). Interaction with the stop threshold: this is evaluated on the SAME filtered velocity signal as stop detection, which locks a choice when velocity falls BELOW StopVelocityThreshold (UpdaterTarget.STOP_VELOCITY_THRESHOLD, seeded from the position-control velocity_threshold and shaped within/across sessions). The two gates partition the same velocity axis in opposite directions, so this value must be >= the operative StopVelocityThreshold: if it is lower, the band (abort_velocity_threshold, StopVelocityThreshold) lets the animal lock a stop while already too fast to hold it, forfeiting every such choice. Because StopVelocityThreshold is dynamic (e.g. shaped 60 -> 8 cm/s by a GAIN updater) whereas this is a fixed absolute, only enable the velocity abort on stages where the stop threshold is already floored below it (e.g. a static stop threshold of 8 with this set to 15); never pair a low fixed abort with an actively-shaped, still-high stop threshold.")] public double? AbortVelocityThreshold { get diff --git a/src/Extensions/InstantiateSite.bonsai b/src/Extensions/InstantiateSite.bonsai index b8a68508..e694e8c7 100644 --- a/src/Extensions/InstantiateSite.bonsai +++ b/src/Extensions/InstantiateSite.bonsai @@ -410,6 +410,11 @@ ThisRewardControl + + + 1 + + OperantLogic.AbortVelocityThreshold @@ -439,20 +444,21 @@ - + - + - + - - - - + + + + + @@ -710,6 +716,11 @@ it.Item2 as EntryPosition) ThisRewardControl + + + 1 + + OperantLogic.AbortVelocityThreshold @@ -738,20 +749,21 @@ it.Item2 as EntryPosition) - + - + - + - - - - + + + + +