Living project record, last consolidated 5 Nov 2026 (repo maintenance pass; earlier full consolidation 21 Oct). Permanent agent instructions (safety rules, working conventions, how to maintain this file) are in .cursor/rules/project-workflow.mdc; this file holds project state, history and decisions. Migrated from the legacy .cursorrules (removed). Where this file disagrees with older notes, this file wins. Older detailed notes live in git history. The repo is public: no private chat, people's names or purchasing/admin details here.
Head-fixed mouse 1D forelimb object-push task, forked from the Mathis joystick-pull LabVIEW rig (Mathis et al. 2017). Trial: forepaw on a capacitive rest pad, then release and push a joystick-mounted object forward into a target band, then auditory cue, delay, spout extends, water. Optional later: axial resistance from a Ledex solenoid. Entry docs: README.md, RIG_INVENTORY.md, MAGNET_COMMISSIONING.md, EVENTS_LOGGER_PLAN.md, parameters/README.md.
- Architecture: see "VI architecture" and "Rig and DAQ".
- Completed (per notes): rest-pad sensor (capacitive, mains-filter sketch confirmed in mouse sessions); EVENTS logger and auto timing file; water/cue/spout reward loop with clean Normal Stop; acquisition overrun fix (a 25–30 min no-animal run with rewards and clean Stop is recorded as done under the rest-pad entry); post and platform position final (20 Oct); Ledex checkpoints 1–4; Ledex FEMM/3D force models and rig-geometry explorer; Ledex holder/link printed and mounted, Ledex-off free-movement check OK (1 Nov); push-axis volts → mm for 0–8 mm (3 Nov). Built and in use (user-reported bench tests, details in their entries): trial start at paw release (Case 5, code 6; every session since 20 Oct), velocity reward mode with the smoothed 200-sample rule (column 28, sessions since 22 Oct; 200 ms start gate skipped), go cue on P0.2 (code 7, 29 Oct), reward on touch (column 29, code 8, bench OK 4 Nov, no mouse session yet); parameter-row mapping fix (5 Nov, short bench passed);
repeat trialsloader step 1 (5 Nov). - Current objectives: (0) finish and bench-test
repeat trials(31st column; see "Repeat trials"); (1) animal-free bench test of the joystick-only mode (30th column, code 9; see "Joystick-only sessions"); (2) fix unpaid successes (water loop busy or missed occurrence; see Known bugs); (3) bench-test the untested 30 Oct–5 Nov training files before using them with mice; (4) decide whether to gate the go cue off in touch mode; (5) analyse the 20 Oct touch-and-release sessions (rewards, release timing, chatter); (6) record the formal release-clause checklist (timeout path, Normal Stop), which was never written up although the clause runs in every session. - Remaining work: Ledex bench (spring curve, release and force tests, finish Checkpoint 5); LabVIEW magnet safety (guaranteed 0 V on all exit paths, logging) before any nonzero
mag; print/fit the wide-window constrainer and the newest cube (mechanical/README.md), record which cube is on the rig, and update the force models if the taller cube is used; movement-timeout path in Case 1 before a nontrivial start box; DataJoint changes (Booleanai2, sensor type per session, trial start = pad release, codes 7–9); shielding/imaging-noise check; calibration items listed under "Task design". - Known bugs / open issues: unpaid successes (code 4 with no cue/water codes 10–18; counted from EVENTS 22 Oct): 3–14 per session in every 20–21 Oct touch-and-release session (e.g. zuchon 20 Oct 150 successes / 137 paid) and 1 in zuchon 22 Oct (trial 8, 298.8 s). Most come 1.5–1.7 s after the previous cue, while the water loop is still in consumption (ITI 1500 ms < cue + water + consumption ≈ 1.7 s), so the occurrence is lost; fix by ITI ≥ 2000 ms or making Case 0 wait for the water loop. 5 of them (incl. zuchon 22 Oct trial 8, loop idle for 180 s) happened with the water loop idle: cause unknown, same class as the magnet-loop miss; inspect both Wait on Occurrence nodes. Acquisition stalls (zuchon 22 Oct:
sample countfrozen ~6 s at 7.6 min and again near 28 min, then caught up; trial 12 lasted 5753 ms) makemake_labview_timing.pyrefuse the session (its first, non-robust clock fit is pulled by the stall outliers; 1980 of 2116 events flagged); robust fit now inmake_labview_timing.py(24 Oct, uncommitted): nominal-slope start + iterative refit, and the 5 % outlier gate is a warning (the 100 ms reward-matching check stays fatal); it recoveredzuchon_20261022_1(worst mismatch 5.6 ms) andzuchon_20261023_1(90.4 ms, close to the 100 ms limit because of long stalls: treat its video sync with care); outputs on the already-good sessions are unchanged (< 0.01 ms). Magnet loop can miss a trial-start occurrence (zuchon 16 Oct after a 328 s pause; zuchon 29 Oct #1 trial 98 with the loop idle ~8 s; TRIAL row and codes 20/21 drop,make_labview_timing.pyrefused the 16 Oct session); parameter-row mapping was wrong before the 5 Nov fix (Case 3 used row n+1, water row n or n+1 by timing, a magnet-loop miss shifted everything by one; see "Parameter-row mapping"; mixed-row files with mice only after repeat-trials bench passes); home shifts ~0.06 V onai1with the magnet on (slot home stop is 1.15 mm on the home side of neutral); code 99 sample ~0.3 s stale; pad lead ~40 cm (target < 30 cm); deferred check of head-fixation clamp to AI GND. - Go cue, bench status (29 Oct): second buzzer wired (reported by user: P0.2 / terminal 49, 1 kohm series, D GND terminal 15; NI-MAX task
GoCue_Dev1) and a stand-alone test VIgo_cue_burst_test.vi(not in the repo) fires a burst audibly (user-reported). Chosen settings: 1 ms on / 1 ms off x 50 (nominal 100 ms). Unverified: actual burst duration (DAQmx write and Wait (ms) overhead on Windows may stretch it), whether it sounds as a tone, mouse audibility (buzzer is 2 kHz per Adafruit or 4 kHz per DigiKey, unmeasured; CBA/CaJ thresholds ~63 dB SPL at 2 kHz, ~38 dB at 4 kHz). The buzzer stops oscillating when its hole is touched or covered (restarts after the line is cycled); never cover it. Built intoPush Behaviour_MCHALABI.vi(29 Oct; user reports the animal-free bench test passed and the first mouse session with it "went super well", no details recorded): third While loop (copied from the test VI) waits on a new occurrence (Generate Occurrence outside the loops, timeout 1000 ms,timed out?through Not into the Case selector); True case runs the burst (For loop N =count, write T, waiton ms, write F, waitoff ms; controlson ms/off ms/count/DAQmx task nameGoCue_Dev1, defaults 1/1/50) and logs EVENTS code 7 (go cue burst start, value 0); Set Occurrence sits in Case 0 in the frame that sets next state 5 (end of thehome TOhold); the loop's stop reads a local of the mainstopbutton. Still to check from the data: actual burst length, code 7 present in EVENTS once per gate pass, order 7 then 6 then 4 in hold-and-release sessions and whether go and reward cues overlap, line left LOW at startup and Stop. Purpose (28 Oct design): signal at the end of the pad hold, not on release, that the cube is pushable; burst modulation gives a different timbre from the reward cue (an active buzzer's pitch is fixed). - Key technical decisions: capacitive pad (Arduino Nano, 0/5 V on
ai2) over FSR and other sensors; plunger linked to the joystick shaft (not a washer target); proportional PVA 0–10 V driver (not a binary switch);Readstays 10 samples; EVENTS file is the source of truth for trials and rewards; trial start/move timeat pad release (Case 5). - Validation status: only items stated as bench-validated, confirmed, passed or run in the sections below are verified. Anything marked pending, built, planned, untested or uncommitted is NOT verified. Record new results in the relevant section and here.
- NI PCIe-6321, SCB-68A breakout (terminal map in
RIG_INVENTORY.md). Saved NI-MAX tasks are incomplete; audit inline channels. NI-MAX backup:ni-max/JoystickPushTask_NIMAX_2026-08-20.nce. - Channels:
- AI task
joysticklickframe_push, continuous 10 kHz, orderai1(Y = push, white, queue idx 0),ai0(X = lateral, red, idx 1),ai2(rest pad, RSE 0–5 V).joystick pos= Y, X, 0, rest pad (idx 3, the slot the lab DataJoint layout calls FSR). - AO0
MagnetPush_Dev1(Ledex via PVA); AO1LickSpout_Dev1(Actuonix L12-30-50-12-I, 0–5 V, own 12 V PSU). - DO
Water_Dev1P0.0;SuccessCue_Dev1P0.1 (terminal 17, D GND 15; Adafruit #1536 buzzer);GoCue_Dev1P0.2 (terminal 49 through 1 kΩ, D GND 15; second #1536). P0.3–P0.7 free. frame counter_Dev1on PFI8 (ctr0); pulse out ctr1 on PFI13 at 50 Hz. Camera runs on the rig PC.- Rest pad terminals: +5 V terminal 8,
ai2terminal 65, AI GND terminal 64.
- AI task
avg joystick and frame trig lick3.viacquisition loop: Read stays 10 samples (1 kHz logged), input buffer 100000 installed, in-loopstop inValue property node deleted,stop allglobal (PushTask Globals.vi) drives all loops. Earlier -200279 overrun is provisionally fixed; a 25–30 min no-animal run with rewards, stopped cleanly, is recorded as done in the rest-pad entry below (an older note said still to be reported; confirm it included rewards). Do not switch Read to 100 (also in the agent rule).- Do not run two simultaneous AI tasks (one AI timing engine). 1 MΩ source impedance ghosts
ai0ontoai2on this multiplexed ADC. - Data on the rig:
C:\Users\Mathis_Lab_1\Desktop\RIG1_DATA(joystick_,REWARD_,TRIAL_,EVENTS_,TIMESTAMPS_LABVIEW_). Python 3.6.4 atC:\Users\Mathis_Lab_1\AppData\Local\Programs\Python\Python36-32\python.exe.
- Rest-pad sensor (capacitive, installed and bench-validated 14 Oct). Arduino Nano (classic ATmega328, not Nano Every) + 10×10 mm copper-tape pad under one layer of tape + 10 kΩ pot (sensitivity knob). Sketch
arduino/touch_pad/touch_pad.ino, wiring and calibration inarduino/touch_pad/README.md. Output is push-pull 0 V / 5 V on D2 intoai2(terminal 65; 5 V = touch; low impedance, no X ghosting); Nano powered from rig +5 V (terminal 8), GND to AI GND (64); unplug USB when running from the rig.rest pad threshold2.5 V (default saved in the VI,3fe0e0f); Case 0 starts only with pad touched AND joystick home. Re-tare the knob (hands away) after any change to the pad or its cover. Normal Stop clean; 25–30 min no-animal run done. - 50 Hz chatter (found in mouse session 1, 16 Oct). With the paw on the pad,
ai2toggled every ~20 ms (high ~13 ms, low ~8 ms; 10,572 touch episodes, only 87 ≥ 100 ms) whenever the knob put the threshold near the paw signal (mostly from minute 16 on, when trial starts stopped). The period drifts against the DAQ clock, so it is mains hum picked up through the mouse, not the camera pulse train. The knob cannot fix it. Sketch fix uploaded, bench-tested and reinstalled on the rig (16 Oct,ai2~0 / ~5 V in NI-MAX): readings averaged over exactly one mains period (20 ms, decision every 10 ms),OFF_FRACTION0.5, touch hold 20 ms, release hold 40 ms, Serial Plotterripplefield. New timing: touch-on ~30–50 ms, release ~50–60 ms.archive/restpad_chatter.py joystick_<name>(moved fromscripts/18 Oct) reports chatter per session (session 1 baseline: 210 chatter seconds, 0.8 % of episodes ≥ 100 ms). - Mains-filter sketch confirmed in mouse sessions (16 Oct, zuchon and blackmamba): 0 chatter seconds, touch episodes median 1.2–1.7 s, ≥ 99 % of episodes ≥ 100 ms (anaconda, old sketch: 210 chatter seconds, median 13 ms). Pad sensitivity, sketch and position are final. Deferred: check with the Fluke whether the metal head-fixation clamp is connected to AI GND 64; if not, add one wire clamp → 64 and compare
ripplewith and without (do this if chatter remains). - Open items: water-drop/licking check on the rig with the spout extended; pad lead is ~40 cm plain wire (target <30 cm); shielding deferred (2-photon first, ephys uncertain; compare imaging noise with the sensor on and off at the first imaging session).
- Data impact: joystick-file column 4 (
ai2,joystick posidx 3) is a ~0 V / ~5 V touch signal (switch lag ~30–50 ms on, ~50–60 ms off with the mains-filter sketch; no force information); file layout unchanged. DataJoint must treat it as Boolean for new sessions and record the sensor type (fsr402orcapacitive) per session; no other DataJoint change needed (column renamed fsr→rest pad). - Rejected for good: AT42QT1010 (recalibrates after ~60 s), Omron E2K-F (too big), M12/M18 industrial sensors, load cell (fallback only), SingleTact and FlexiForce. Earlier FSR 402 history:
archive/FSR_rest_pad_history.md. - Mice (back on water restriction Wed 14 Oct). Thu 15 Oct: ~25 min head-fixation per mouse at 4 µl rewards (habituation to the smaller volume; earlier 8 µl). Fri 16 Oct: first rest-pad hold + micro-push session for 3 mice (numbers below from the files; 4 µl rewards):
anaconda(02_micro_push_day1_restpad.txt: home 2.47–2.56, success < 2.47,home TO100, ITI 2000; 29 min): 37 rewards / 161 trials (23 %), 31 of them in the first 15 min; trial starts stopped after ~20 min (old sketch, 50 Hz chatter). Home lower bound = success bound, so trials starting at ~2.47 V succeeded on noise (2 rewards with a 3 mV excursion). In 28/37 rewards the pad stayed touched during the push (other paw, or paw on cube and pad together, cube was in the middle hole).zuchon(_home2p44.txt: home 2.43–2.52, success < 2.43, ITI 2000; 28 min;_2is a 10 s false start, discard): 4 rewards / 85 trials, all four the intended sequence (pad released 150–390 ms after trial start, then push). Joystick/cube moved during the session (home drifted 2.45 → 2.47 V; needed ~40 mV push). From minute ~11 the paw never returned to the pad (no touch for the last 16 min) and kept pushing the cube (45–52 push bouts per 5 min, 197/204 outside trial windows): cube too close.blackmamba(_home2p475.txt: home 2.475–2.51, success < 2.47,home TO50, ITI 1500; final position; 34 min): 19 rewards / 250 trials (7.6 %; user recalled ~37), rising later (14 after minute 17). 12/19 rewards with the pad released first (0.1–2.8 s), 7 with the pad touched throughout (check video). 169/231 failed trials: paw rested on the pad for the whole 3 s window. 179 push bouts (median 78 mV, ≫ the 16 mV needed), only 29 inside trial windows; most outside pushes had no pad touch in the second before (paw went to the cube without a pad hold). All 250 rows of the file were used (last trial at 34.0 min).- Next: next session in the final position with
_home2p475settings but a longer file (≥ 500 rows; check what the VI does past the last row) and considermove time3000 → 5000–8000 ms (pushes starting within 3 / 5 / 8 s of trial start in blackmamba: 17 / 28 / 40 trials). Check video for pad-touched successes. Water earned is small (≈ 150 / 16 / 76 µl), so supplement to the daily allowance. - Data issues: zuchon's magnet loop missed trial 84 (after a 328 s pause: no code 20/21, TRIAL file 84 rows vs 85 trial starts in EVENTS), so
make_labview_timing.pyrefuses that session; anaconda and blackmamba timing files build (drift +4.5 ppm, worst mismatch 6–8 ms). Find the cause (magnet-loop Wait on Occurrence) before any nonzeromag. Joystick/cube position: 16 Oct record was 6 mm from the platform edge and 7 mm from the rest-pad edge; after the 20 Oct post/platform move and the 1 Nov Ledex mount the cube at home is 2 mm from the rest platform (3 Nov, user). Reuse the current distance on any other rig or mount. Changes made during/after it: the cube is mounted in the hole nearest the rest pad (not the middle hole) so the paw cannot touch cube and pad together; the rest pad moved slightly to the right so only the right paw can reach it. Remeasure home voltage after these moves. Stage-1 procedure, if used instead: use onlydeliverWater_RIG1_cue.vi(50 ms cue then water),Water on time (ms)240 (~8 µl), extend spout to 3.0 V before the first reward and retract to 0 V at the end (that VI does not move the spout). Keep the push VI closed. Cap 25 min and the approved water limit; stop when licking fades; tally rewards. Pad-contact shaping fallbacks01_pad_contact_shaping_50ms.txt/_250ms.txt(home TO50 / 250 ms, otherwise identical) are untested with the capacitive pad; do a bench check first. First pilot (28 Sep) and shaping attempts showed contact activation was too rare for contingency learning. - Ledex
195224-230+ PVA 1.6 (0–10 V), followingMAGNET_COMMISSIONING.md. Checkpoints 1–4 DONE (11 Oct). Mechanical mount (1 Nov 2026): holder, window constrainer, collar, link printed and installed; home stop is the 1D-axis constrainer (not a separate shim). Wide-window constrainer (2 Nov, CAD only, not printed or fit-checked):mechanical/joystick-1d-axis-constrainer/..._ledex_wide_window(+make_wide_window.py) replaces the 16 mm window: home wall open over the full 26 mm cavity width from the frame top (Z 25.41) to the ceiling, socket ring closed at the bottom; the printed 16 mm window part is archived.rig_geometry.pyupdated (ARCH_HALF_W13, new file name); static clearance check passes, the moving-parts check and the explorer/figures were not rerun (matplotlib missing). Ledex-off hand check: full return to home, no binding or collisions (Checkpoint 5 partial).ai1volts → mm measured (3 Nov, user, current mount): home ~2.47, 2 / 4 / 6 / 8 mm = 2.425 / 2.365 / 2.305 / 2.235 V; fit 0.0295 V/mm (33.9 mm/V, R² 0.995), slightly steeper further out (0.0225 V/mm over 0–2 mm, 0.035 over 6–8 mm); details and figure incalibration/joystick-volts-mm.md. At home the cube is 2 mm from the rest platform (replaces the 16 Oct 6 mm record). Not yet measured: 10 / 15 mm. Next bench (Ledex still off,mag=0): gram-scale spring curve, 10× release test (±0.035 V), powered hold/release and force at 5 V / 10 V; complete Checkpoint 5; LabVIEW magnet safety before any nonzeromag.- Wiring (verified): 12 V Goobay + → 1 A ESKA fuse (TC-R3-32AA1 holder) →
D, − →F; SCB 22 →H, 55 →G(twisted); coil onC/A(no polarity); EnableJon a wire with a clip that is clamped on theDscrew to enable and removed to disable.K,M,Eempty. Supply 11.91 V, LED red when disabled. Leave the supply unplugged and the clip off between tests. - Trimmers (left to right, from the datasheet legend): rising ramp, falling ramp, Imax (3), Imin (4), dither. Imin and Imax are ~30 turns counterclockwise (slip clutch, no hard stop); counterclockwise = minimum confirmed for Imin (≤ 0.006 A at 0 V with Enable on).
- Coil current vs AO0 (plunger seated, cold, 2–3 s pulses, Fluke 10 A DC in series): 0.5 V 0.053, 1 V 0.074, 2 V 0.106, 3 V 0.121, 4 V 0.147, 5 V 0.180, 6 V 0.201, 8 V 0.273, 10 V 0.310 A. No sound or heat, clean release. 0.58 A cannot be exceeded at this Imax, so no command clamp is needed for current; Imax stays at minimum for now (raise clockwise in small steps only if more force is needed).
- Pulse method used: set ao0 in NI-MAX Test Panels (Analog Output tab,
Dev1/ao0, neverao1) with Enable off, clip onDfor 2–3 s, unclip, back to 0 V, 10 s off. Measure H–G resistance only with the SCB end disconnected (a live ao0 makes the Ω reading fluctuate). Move the Fluke red lead back toVΩafter current tests. - Plunger: 60° (cone-ended) type; the cone end goes inside against the matching taper and the plain pull end protrudes (tip still protrudes when seated); never reverse it, never run it loose. Without the plunger no pull could be felt even at 10 V. Design chosen: plunger linked to the joystick shaft (see "Plunger link"); the washer target is rejected (re-checked on the measured rig, 15 Oct).
- Wiring (verified): 12 V Goobay + → 1 A ESKA fuse (TC-R3-32AA1 holder) →
- Datasheet (STA 20 × 40 mm pull, 30 AWG, 1491 turns; PDF and digitized 60° curves in
simulations/ledex_femm/datasheet/): seated holding force 12.8 N (60°) at 0.57 A; plunger pull at 0.57 A 5.35 / 3.38 / 2.14 / 0.78 N at 1.5 / 3 / 5 / 10 mm stroke. Seated pull end is 23.81 mm beyond the body face (M14 boss 9.52 mm on the plunger side). Nothing is published for an external target. - Ledex force models (
simulations/ledex_femm/, README there; FEMM 4.2 inC:\femm42on the rig and under Wine on the Mac;femm_run.pyruns it in parallel,calibrate.pyfits it). FEMM (2D) calibrated against the datasheet curves (12 Oct): error ±15–20 % incl. a held-out curve; still 1.4–2× high beyond 8 mm stroke. One Ledex geometry for all models:simulations/ledex_femm/ledex_geometry.py(body, plunger, slot, cross hole; shared by the reference CAD, rig model, FEMM and 3D). FEMM uses axisymmetric stand-ins for the slot and cross hole (plunger force changed ≤ 2 %, calibration error 0.178 → 0.180, kept); the 3D model now uses the real slotted, cross-drilled tip (washer pull: short towers +15–25 %, tall towers −7 %). Washer forces come from the 3D model (fea3d.py, NGSolve venv/tmp/ngenv, setFEA3D_THREADSwhen running sweeps in parallel;sweep3d.py, incl. theoffsetgroup: tower moved vertically, half model); treat as ±40 %. Previous results are inresults/*/axisym_tip/. Holder axis range 38–78 mm is one setting (HOLDER_H_RANGEinrig_geometry.py) used by the CAD, forces and explorer. Mathis 2017 gives no force at the joystick (replaced the stock ~4 N base spring to get 0.1–0.2 N = 10–20 g). - Rig geometry (15 Oct;
rig_geometry.py, README "Rig geometry"). Built from the measured joystick component and component holder CAD (mechanical/joystick-component/,joystick-holder/); world X = push (+ away from the mouse), Z up from the breadboard, origin under the joystick axis. Shaft base Z 43.18 (screw head glued in the shaft's bottom bore, screw into the stub, cone seated on the stub top), constrainer 15.41–71.41 on the holder frame; its home-wall arch (top Z 63.41) is cut square up to the ceiling Z 68.41 in the window revision (superseded 2 Nov by the wide window,joystick-1d-axis-constrainer/make_wide_window.py), cube contact Z 135.6. Pivot assumed mid-body, 23.5 mm below the shaft base (bounds 19.3–27.3); rig check: push the cube 10 mm against a ruler, r = shaft travel at height z / 10, pivot depth = (92.4 r − z)/(1 − r). The shaft is in the cube's front hole (push face 2.05 mm from the axis,CUBE_HOLEinrig_geometry.py; forces recomputed 16 Oct: change < 1 %; models switched to the narrow cube 17 Oct: effective mass 4.2 g, forces unchanged). A 15 mm push tilts the shaft 7.4°; lever predicts ~0.49 V per 10 mm push, but the 3 Nov measurement gives 0.295 V per 10 mm (0.0295 V/mm), so the lever/sensor model overestimates the voltage scale by ~1.7×. The constrainer slot's home end stops the shaft 1.15 mm (at the cube) on the home side of the spring's neutral, so the magnet pulls the handle onto that stop at home (home shifts ~0.06 V onai1with the magnet on): move the stop to neutral (slot end Y −1.5 or a 0.5 mm shim) or record it. - Plunger link (chosen design;
plunger_link.py,rig_forces.py, README "Plunger link" and "Washer target vs plunger link"). Rigid 14 mm link (1.8 × 5 mm bar in the plunger's 2.5 × 9.5 mm tip slot) pinned through the plunger's factory 2.5/2.6 mm cross hole 4.76 mm from the tip (datasheet F34; nothing to drill; slot turned vertical, 2.5 mm × 10 mm brass/A2 pin) to a collar fork pin 6 mm off the shaft axis on the home side (collar: ring with 3.7 mm bore, fork, one M2 set screw on +Y; nothing may stick out on +X, it would hit the constrainer near 15 mm). Ledex body and plunger modelled from the drawing inmechanical/ledex-195224-model/(plunger 59.7 mm long, mesh masses match the datasheet). Link height limits: shaft z 15 to 20.3 (window top); height barely changes force. Recommended: link z 17.5 = Ledex axis Z 60.7, home stroke 3 mm at neutral = body face X −42.05; moving parts clear by ≥ 2 mm from the home stop to 15 mm (bar play 0.35–0.4 mm in the slot and fork); stroke 3.0 → 8.35 mm over 15 mm. Resistance at the cube, push 0 → 15 mm: 1 V 2.3 → 0.64, 5 V 13 → 3.8, 10 V 40 → 11, 0.58 A 125 → 39 gf (pivot bounds −7/+6 %). Washer target at its best layout (Z 58.7, 1 mm gap): 10 V 0.75 → 0.06 gf, i.e. the link is ~53× stronger. Spring: fitted, no preload (force = k × push, k adjustable); plunger friction at the cube is 1.2 / 2.3 / 3.0 gf (mu 0.15 / 0.3 / 0.4), leaving a dead band where k × push < friction. Model at mu 0.3: k 0.2 gf/mm never returns (stops at +8.4 mm), 0.5 gf/mm returns in ~60 ms, 0.67 gf/mm in ~48 ms; measure the real k on the bench (gram scale). The explorer has no preload slider (removed 18 Oct). Its sliders show physical distances rather than world coordinates: stroke length (mm) (plunger stroke from seated at neutral, 0 = fully seated; link option) or distance to target (mm) (gap to the washer tower at neutral; washer option), magnet height (mm) (height distance above the pivot), and pivot depth (mm) (relative to the bottom face of the TS-1D1S00A component, 2.0–10.0 mm, default 5.77 mm). All sliders span the feasible range (recomputed from the feasibility checks; beyond a 12.5 mm stroke the pull is extrapolated) and can also be typed. Moving the pivot depth changes only the magnet-height reading and range, never the magnet position. A "set default" button resets every slider (layout, pivot, push, drive, spring, friction). A button switches all forces (readout, plots, stiffness) between gf and N (stiffness as N/m). Push at the cube runs from the home stop to 20 mm (the slot would allow ~36 mm at the cube). Explorer:figures/rig/rig_explorer.html(all parts, sliders, live forces, design map; force plots use linear gf axes, lateral Y fixed at 0). - Final install rules: PVA outside the mouse box; command wires 30–100 cm twisted; coil cable 1–2 m twisted and away from
ai0/ai1/ai2; short pulses and long cool-downs; stop on heat, smell, binding, wrong direction or failure to release; re-check 0 V and a small command after moving anything. Random-force sessions need this proportional driver (not a binary switch). - Adjustable Ledex holder (15 Oct;
mechanical/ledex-holder-adjustable/, generated bymake_ledex_holder.py; printed and mounted 1 Nov 2026). Base with two M6 slots along X (33 mm travel, 25 mm or 1 inch grid), upright with lateral M4 slots (±12.75 mm) and two long vertical M3 slots (axis Z 38–78, 1 mm scale), carriage on the M14 boss held by 4 × M3 above and below the case into captive nuts (adjustable from outside with the Ledex fitted); link collar (gap and stroke gauges archived inarchive/ledex-gauges/, 17 Oct, and removed from the generator) and bar,fit_test(print first: holes print ~0.3–0.4 mm small, cube's 3.4 mm holes just pass the 3 mm shaft; allowances in theFITblock). Clearances to the joystick holder/constrainer: link layout ≥ 16 mm, washer layout ≥ 4.3 mm;make_ledex_holder.py checkreruns the static and moving checks. - Next (
ai1volts → mm done 3 Nov): optional caliper-check plunger and M14 nut; bench without mice — spring curve with a gram scale at the cube (0 / 5 / 10 / 15 mm, with and without the plunger, Ledex off; preload ≥ 2× the difference), release test 10× from 15 mm back to home within ±0.035 V onai1, hard stop holds at 10 V with clean release at 0 V, scale force at 5 V and 10 V at a 5 mm push to calibrate the model; finish Checkpoint 5 (powered direction and release). LabVIEW magnet safety (guaranteed 0 V on every exit path, logging) still to do before any nonzeromag. - Rest-pad release clause (built and diagram-reviewed 19 Oct, committed
3046091; runs in every mouse session since 20 Oct and pays touch-and-release trials, but the formal animal-free checklist below was never recorded). Problem in session 1:move timestarted at the Case 0 pass while the paw stayed on the pad, so the mouse started many trials it never completed. Case 0 next state is now 5; new Case 5 waits for paw release only (ai2=joystick posidx 3 <rest pad threshold, read local variable; joystick ignored); on release it writes Tick Count →trail start(themove timeclock, moved from Case 2), logs new EVENTS code 6 (paw release), then → Case 1. Case 2 keeps Set Occurrence (magnet loop:Trial #+ TRIAL row) and code 3 at the start zone, somake_labview_timing.py(code 3 must equal TRIAL samples) is unaffected. Case 1 keeps the legacy start box = the "start position" (later halfway between home and end); paw re-touch does not reset. Before making the start box nontrivial, Case 1 needs amove timetimeout → Case 0 (write current time tostart time, log code 5, setin trigROIF), otherwise failure to reach a separated start zone would wait forever. Bench test withparameters/training/02_micro_push_day1_restpad_home2p44-2p49.txt: home 2.44–2.49 V held for 100 ms, full-range start box 0–5 V (irrelevant), success 0–2.35 V. Next: animal-free bench test (finger held on pad: no trial; lift: code 6 then trial start; lift + push: reward; movement timeout back to Case 0; Normal Stop), then record result. Data impact: code 6 marks paw release + ~50–60 ms sensor lag; code 2 values now include 5. - Head-fixation post moved (20 Oct). The post was raised a bit and moved back so the first two mice can reach and use the rest pad more easily, and so the body or arm does not touch the pad by accident while the mouse works the joystick. Home voltage, pad-touch behaviour and the cube/pad reach may change: remeasure home before any joystick session. Rest platform repositioned at the same time. Verdict after the 20 Oct session: fine for all 3 mice, so the post and platform position is final (no more changes).
- Spout at Run: set
Session start spout (V)= 3.0 V before Run. Day-one file holds extend and retract at 3.00 V, so the spout stays extended. Normal Stop retracts to 0 V. - Home voltage depends on whether the shaft and object are fitted. Remeasure before loading any file. Current release-gated day-one file:
parameters/training/02_micro_push_day1_restpad_home2p44-2p49.txt: home 2.44–2.49 V,home TO50 ms, start box 0–5 V (irrelevant), success 0–2.40 V, ITI 1500 ms,move time3000 ms, water 130 ms (~4 ul), cue 50 ms, delay 0, consumption 1500 ms, 250 identical rows,mag=0, spout columns 0 V. Earlier02_micro_push_day1_restpad.txtand_home2p44.txtpreserve the 16 Oct settings;02_micro_push_day1_5s_8ul.txtis the older 2.25 V FSR-era file.02_micro_push.txtand stages 03–08 still use the 2.50 V /home TO250 ms calibration. - Touch-and-release-only file (run 20 Oct with all 3 mice; user reports it went really well; numbers from the files not yet analysed, rewards/release timing/chatter still to check; file committed).
parameters/training/01_pad_touch_50ms_release.txt(250 rows, 27 columns checked): home, start and end boxes all 0–5 V push and lateral,home TO50,end TO0, so the joystick is irrelevant; pad touch → release (Case 5, code 6) → trial start → reward as soon as Case 3 runs. Rest as the day-one file (move time3000, ITI 1500, 130 ms water, 50 ms cue, spout columns 0 V). Expect reward ~50–60 ms after real lift-off; setSession start spout (V)3.0 first. Bench test (finger on pad, lift: code 6, one cue and one water pulse) before a mouse; needs the release-clause VI. - 100 and 200 ms hold variants (21 Oct):
parameters/training/01_pad_touch_100ms_release.txtand01_pad_touch_200ms_release.txt(both committed) are identical to the 50 ms file excepthome TO(column 4) = 100 / 200 in all 250 rows (27 columns checked each). Run 21 Oct (anaconda 100 ms; zuchon and blackmamba 200 ms; see the session map); no separate bench test recorded. - Velocity reward mode (built and partly bench-tested 22 Oct on the rig, committed
0f05f87; first mouse sessions 22 Oct, see "Velocity rule revision"). Optional success rule, off when the new 28th columnvel threshold= 0 (27-column files load 0). Velocity = abs(d push) + abs(d lateral) over a 50-sample lag (50 ms at 1 kHz; Feedback Node delay 50 in the middle loop, outside the case structure;velocityindicator), units V per 50 ms. Case 3 False:vel thresholdadded to the end-box Unbundle By Name;(velocity > thr) AND (thr > 0)is ORed onto the "dwell met" wire before the next-state case, so any single pass above threshold pays (end box still works too). Loader:exp valuesandexp para arrayclusters gotvel thresholdas the LAST entry (default 0), loader case 27 bundles it, Array Subset length 27 -> 28. Lag choice from data (8 mouse joystick files, 20-21 Oct, paw on pad, cube untouched, 224 min): noise 99.99th pct 7.8-8.0 mV and max 10-12 mV at every lag (10/20/30/50 ms), so a longer lag adds signal but no noise; onsets above threshold at lag 10: 0.009 V 42, 0.010 V 5, 0.011 V 1, 0.012 V 0 (lag 50: 43 / 4 / 1 / 0). A slow ~23 mV micro-push in veltest3 never paid at lag 10 (peak 0.0100) but peaks 0.0146 at lag 30 and 0.0180 at lag 50. Threshold 0.012 kept; 0.015 file is the fallback if rewards appear without touch. User-reported bench results:velocityidle ~0.000-0.008, taps above 0.012; veltest3 paid on touches; with delay 50 the micro-push paid and the user confirmed it works. Not yet checked or recorded: 27-column file giving threshold 0 and old behaviour, no-touch timeout with table vibration, sideways nudge, Normal Stop, a mouse session. Filestraining/02_micro_touch_velocity_200ms_thr0p012.txt/_thr0p015.txt(500 rows, 28 columns checked): home push 2.43-2.53,home TO200, end box 0-1 V (unreachable, only velocity pays),move time5000, water 130 ms, spout columns 0. Re-centre the home box on the measured rest voltage before a session (rest was 2.477-2.480 V on 21 Oct). - Reward on touch (4 Nov; VI built 4 Nov (steps 1-4, 4a/4b screenshot-reviewed, 4c not seen); animal-free bench test done 4 Nov, user reports "works all gucci" (release and touch mode, no per-step numbers recorded; EVENTS codes 7/8/6 and the held-finger no-second-reward check not independently verified from the files; go cue and reward cue sound nearly together in touch mode is an open design point; no mouse session yet). Step 3 built 4 Nov (screenshot: Select(
touch mode, 8, 6) into the code input of the Log Event in Case 5 True; terminal order to be confirmed by user). Step 2 built 4 Nov (screenshot-reviewed: Index Array on the brown parameter wire with theTrial #local, Unbundlereward on touch, Not Equal To 0?, Or with the Less? output, into the inner case selector; not run). Step 1 (loader) built 4 Nov; user reports Run arrow OK, the touch file loadsreward on touch1 andvel threshold0, a 28-column file loads 0. Optional 29th columnreward on touch(0 = pay on release as now, 1 = pay once thehome TOhold completes, paw still on the pad); 27- and 28-column files load 0, so existing sessions are unchanged. Touch-mode files need all 29 columns (vel thresholdcolumn 28, usually 0). VI steps, one at a time with user confirmation: (1) loader:reward on touch(DBL, default 0) as the LAST entry of theexp valuesandexp para arrayclusters, loader case 28 bundles it, Array Subset length 28 -> 29; check a 28-column file gives 0. (2) Case 5:reward on touch≠ 0 -> Booleantouch mode, ORed onto the release condition, so in touch mode Case 5 passes at once and its existingtrail startstamp and -> Case 1 run unchanged. (3) Case 5 logging: Select(touch mode, 8, 6) into the Log Event code, so new EVENTS code 8 = touch-mode pass and code 6 keeps meaning paw release (code 7 is the go cue). (4) Re-arm (revised 4 Nov after seeing Case 0; a shift register would need tunnels through every case): Boolean front-panel controlrearmed(default True) with local variables. 4a: Case 0 computesrearmedOR NOTpaw on rest padeach iteration and writes it back. 4b: that value is ANDed with the final gate And (dwell met AND ITI elapsed) before the Case 0 True/False selector, so the pass to Case 5 is blocked until the paw has lifted. 4c: Case 5 True tab writesrearmed= NOTtouch mode, so a touch-mode pass clears it and a release-mode pass leaves it True. Release mode is unaffected. Stop withrearmedFalse is harmless (Case 0 sets it True at the first lift). Open point: in touch mode the go cue (code 7, set at the end of the hold) and the reward cue fire almost together; decide whether to gate the go cue off in touch mode. Bench test (animal-free) after the build: 28-column file behaves as before (lift pays, code 6); touch file pays at hold end with the finger still on (code 8, one cue, one water pulse), a held finger gets no second reward until lifted and re-held, Normal Stop clean. - Joystick-only sessions (5 Nov; VI built 5 Nov, steps 1-4 done by the user, Run arrow OK; Case 0 screenshot-reviewed after 2a-2c, Step 3 and Step 4 not seen by the agent; bench test PENDING, nothing verified at runtime, no mouse session; VI committed in
07041f3, bench file renamed86f2bc4). The step list below was the build plan; step 3 was implemented as a Case Structure onjoystick onlyaround the go-cue Set Occurrence in the Case 0 right-hand True tab (Set Occurrence in the False tab). Optional 30th columnjoystick only(0 = rest-pad sequence as now; 1 = joystick-only). 27- to 29-column files load 0. Mode 1: Case 0 gate = joystick in home box heldhome TOAND ITI elapsed (pad term andrearmedterm bypassed), go-cue Set Occurrence skipped (no burst, no code 7), Case 5 passes at once, stampstrail startand logs new EVENTS code 9 (joystick-only arm), then Cases 1-4 unchanged (start box, end box /end TO,vel threshold,move time, reward columns). Column 29 is ignored in mode 1 (code 9 wins over 8/6;rearmedmay still be cleared by a 29 = 1 row but Case 0 ignores it in mode 1). Restart after success or timeout needs only the joystick back home forhome TOplus the ITI. Caveat: withvel threshold> 0 in mode 1, movement inside the home box just before arming is inside the 200-sample look-back and can pay at once (same class as zuchon #29). VI steps (designed from the 4 Nov Case 0 / Case 5 screenshots, one at a time with user confirmation): (1) loader:joystick only(DBL, default 0) LAST inexp valuesandexp para array, loader case 29 bundles it, Array Subset 29 -> 30; check a 29-column file gives 0 and the new file 1. (2) Case 0: grow the existingtimeoutUnbundle By Name (bottom left, on Index Array of the brown wire withTrial #) to also outputjoystick only, Not Equal To 0? ->joystick only; Or(paw on rest padcompare,joystick only) replaces the pad input of the And that combines it with the home-box And (LED and re-arm Not keep the raw pad wire); Or(re-arm Or output,joystick only) replaces that input of the last And before the right-hand selector. (3) Case 0 right-hand True tab: put the go-cue Set Occurrence (!node on the teal occurrence wire) in a case structure that runs only when NOTjoystick only. (4) Case 5: grow thereward on touchUnbundle to also outputjoystick only, Not Equal To 0?, Or onto the existing Or before the inner selector; in the True tab Select(joystick only, 9, existing 8/6 Select) into the Log Event code. Bench file:parameters/push_bench_joystick-only.txt(500 rows, 30 columns checked). Bench test (animal-free) after the build: 29-column release and touch files behave as before (codes 7 then 6 / 8); joystick-only file: no pad contact at all, home 200 ms then push ≤ 2.40 V pays (code 9, no code 7, one cue, one water pulse), no push times out after 1 s (code 5) and the next trial re-arms with the joystick home and no pad touch, a finger held on the pad changes nothing, Normal Stop clean. - Repeat trials (5 Nov; step 1 DONE, user-reported (loader loads 0 / 1 / 0 as expected); step 2 DONE and screenshot-reviewed (new case structure in the timeout tab,
repeat trials≠ 0 selector,trial row->Trial #write; wire source and empty False tab to be confirmed by the user) (Case 3 inner True tab = timeout: constant 0 next state, Log Event code 5;move timeUnbundle on Index Array withtrial row); bench test PENDING, runtime repeat logic NOT tested; VI uncommitted). Optional 31st columnrepeat trials(index 30; 27- to 30-column files load 0). 0 = next trial uses the next row (as now). 1 = when this row's trial times out in Case 3 (move time, code 5), the next trial uses the same row again; a success still advances. Mechanism (relies on the row-mapping fix): after arming,Trial #=trial row+ 1, and the only writer is Case 5 True; in the Case 3 timeout tab, if the running row'srepeat trials≠ 0, writeTrial #=trial row, so the next arming copies the same row intotrial row. Only the middle loop writesTrial #and its states run one at a time, so no race; magnet and water loops readtrial rowand are unchanged; the Case 0 last-row gate still works. Data impact: with repeats, trial n is not row n and the front-panelTrial #shows the next row index, not a trial count; the EVENTS code 3 value is the 1-based row number (count trials from codes 6/8/9). VI steps (one at a time, user confirms each): (1) loader:repeat trials(DBL, default 0) LAST inexp valuesandexp para array, loader case 30 bundles it, Array Subset 30 -> 31; check a 30-column file loads 0 andpush_bench_repeat-trials.txtloads 1 on row 1 and 0 on row 4. (2) Case 3 timeout tab (screenshot of Case 3 needed first): grow the Case 3 Unbundle By Name (Index Array withtrial row) to also outputrepeat trials, Not Equal To 0? into a new Case Structure inside the timeout tab only; True tab:trial rowlocal (read) ->Trial #local (write); False tab empty. Bench test (no animal),parameters/push_bench_repeat-trials.txt(40 rows, 31 columns checked; the row-mapping bench rows with columns 29-30 = 0 andrepeat trials0 on rows 4, 8, ..., 1 elsewhere): row 1 lift pays (code 3 = 1); row 2 lift without push times out at 2100 ms twice with code 3 = 2 both times, then lift + push pays 52 ms; row 3 lift pays; row 4 timeout (2200 ms) is followed by row 5 (code 3 = 5); thenpython scripts/check_row_mapping.py EVENTS_<name> parameters/push_bench_repeat-trials.txt(now follows repeats; regression run onbenchtestafterfix1unchanged, 0 problems; synthetic repeat sessions: correct one passes, column-ignored one flagged) expects 0 problems; regression: a 30-column file still advances after a timeout. - Parameter-row mapping (5 Nov; diagnosed from EVENTS; fix built 5 Nov in the VI, committed
924bba8; short bench run PASSED: sessionbenchtestafterfix1, 14 trials,check_row_mapping.py0 problems: code 3 = n, water 50 + n, even-row timeouts 2100/2302/2402/2701 ms vs 2000 + 50 n; odd trials paid, so Case 3 uses row n. NOT yet covered: slow even-row successes, a > 5 min pause, past row 40, a 29-column or joystick-only file regression, a mouse session; screenshots of Case 0/2 not reviewed). Required: trial n (n-th arming, code 6/8/9, 1-based) uses row n everywhere (Case 0 gate, Cases 5/1/3, magnet, water). Found with the mixed file02_mixed_touch1_cube1_..._5s_1p5ul_3ul(zuchon 29 Oct #1, 30 Oct #2); identical-row files hide it. One model fits every trial of both sessions (per-trial EVENTS replay;scripts/check_row_mapping.pyreproduces it): the magnet loop is the only writer ofTrial #(starts at 0, +1 within ~1 ms of the Case 2 occurrence, before its code 20 log), readers index the parameter array 0-based with aTrial #local. Hence Case 0 and Case 5 (read before Case 2) use row n, Case 3 (end box,end TO,vel threshold,move time) uses row n+1 (30 Oct #2: all 7 odd trials timed out at 5000 ms, all 7 even trials paid 4-8 ms after code 3). The water loop's parameter read is not ordered after its Wait on Occurrence:watertimecame from row n when the success was < 20 ms after code 3 (66 of 69) and from row n+1 when ≥ 200 ms (27 of 27), mixed in between; this fits a read at the start of a water-loop iteration that restarts about every 100 ms (inferred; the loop's timeout and read position not yet seen). 29 Oct trial 98 has code 3 but no code 20/21 and no TRIAL row (119 code 3 vs 118 TRIAL rows): the magnet loop missed that occurrence while idle,Trial #stayed one behind, and from trial 98 Case 3 used row n, Case 0/5 row n-1, water row n-1 or n. Magnetmag/magtimerow unknown (equal in all rows). Data impact: in those two sessions label trial types by the Case 3 row (behaviour), not by file row; code 15 is the water actually given. Fix design (from the data; diagram not yet inspected, may change after step 0):Trial #= trials armed so far = 0-based row index for the next trial; its only writer is the arming (Case 5 True tab: readTrial #, write it to a new indicatortrial row, then writeTrial #+ 1); the magnet-loop increment is deleted. Case 0 and Case 5 keep theirTrial #locals (they run before the arming write: row n). Every reader after arming (Cases 1-4, magnet loop, water loop) switches its local totrial row(Select Item), and the magnet and water loops read it once, after their Wait on Occurrence returns, feeding all frames from that one Index Array.trial rowonly changes at the next arming, which needs the Case 0 hold and ITI, so it cannot change under a loop that just woke. Occurrences are kept (no change to reward timing; lost occurrences stay a separate bug but no longer shift rows). Code 3 value =trial row+ 1 (trial number). After the last row: Case 0 gate ANDsTrial #< Array Size(parameter array), so no trial is armed past the last row and the VI idles until Normal Stop (today Index Array past the end returns an all-zero cluster: home box 0-0 V, so no arming in practice, but undefined).Trial #must be 0 at each Run (check the startup frame). Alternative kept in reserve: queue/notifier payload instead of occurrence + local. Step 0 done 5 Nov (screenshots and Find > Local Variables):Trial #is a DBL indicator whose terminal is wired to a 0 constant (reset at Run, presumably; the bench test checks it via code 3 = 1 on trial 1). Its 8 original locals: water loop read outside its case structure; magnet loop read inside the False case (Index Array+ Unbundlemag/magtime/light, runs after the wait, Wait on Occurrence timeout 100 ms); magnet frame 0 read -> Increment -> write (the only writer); Case 0, Case 1, Case 3 and Case 5 one read each. Water loop:Index Array+ Unbundle (watertime...spout retract) also outside its case structure (right side), same stale read; this confirms the water mechanism above. Case 2: code 3 Log Event value is a constant 0; Set Occurrence bottom left; a node on a thick blue wire top left (not part of this fix). Water-loop read after the wait: wrap the row local in a one-frame Flat Sequence and wire the Wait'stimed out?wire into it (no nodes moved into the case structure). Build steps (one at a time, user confirms each; do not run the VI between steps 1 and 2): (1) DONE 5 Nov, screenshot-reviewed: front-panel copy ofTrial #namedtrial row; Case 5 True tab:Trial #read ->trial rowwrite and -> Increment ->Trial #write; (2) DONE 5 Nov, user-reported (Run arrow OK,Trial #now 8 locals, the only write in Case 5 True): delete the magnet-loop increment (frame 0 read, Increment, write); (3) (3a DONE 5 Nov, user-reported: Case 1, Case 3 and magnet-loop locals switched; 3b water loop DONE 5 Nov, user-reported, not screenshot-reviewed) Case 1, Case 3, magnet-loop and water-loop locals ->trial row(Select Item; Case 0 and Case 5 keepTrial #), with the flat-sequence trick in the water loop only; (4) DONE 5 Nov, user-reported, not screenshot-reviewed: Case 2 code 3 value =trial row+ 1; (5) DONE 5 Nov, user-reported, not screenshot-reviewed (Array Size + Less? + extra And before the right-hand selector): Case 0 last-row gate; (6) resetTrial #at Run: the terminal already has a 0 constant, so probably nothing to do. Then the bench test. Bench test (no animal):parameters/push_bench_row-mapping.txt(40 rows, 28 columns checked; odd rows touch-and-release, pay at release; even rows home push 2.43-2.53, need push ≤ 2.40;watertime= 50 + row ms,move time= 2000 + 50 × row ms, all else equal,mag0,magtime25). Lift without pushing on most trials (odd rows pay at once, even rows time out at their own length), push on a few even rows, include one > 5 min pause, run past row 40 (expect no 41st arming), Normal Stop. Thenpython scripts/check_row_mapping.py EVENTS_<name> parameters/push_bench_row-mapping.txt: expect 0 problems and code 3 value = n for every trial. The magnet row cannot be checked from EVENTS whilemag= 0 andmagtimeis fixed (diagram review only). - Velocity rule revision (analysis 22 Oct; NOT built, NOT bench-tested). Sessions
zuchon_20261022_1(33 trials, 5 code 4, 4 paid, see Known bugs) andanaconda_20261022_1(40 trials, 1 paid), filethr0p012(offline replay of the 50-sample raw velocity crosses 0.012 one sample before every code 4, so the replay is sample-exact). Problem: raw 1 kHz noise (SD ~0.9 mV push, ~1.0 mV lateral) makes the raw velocity noise floor 0.0077 (median of 5 s window maxima), max 0.0100, so slow or gentle touches stay under 0.012. Video (camera 1) confirmed 4 missed touches: zuchon #20 (slow push, peak 0.0110) and #23 (gentle grasp of the cube top), anaconda #1 and #16. Proposed rule: 10-sample moving average on push and lateral before the delay, look-back 200 samples, threshold 0.006, and ignore velocity until 200 ms after trial start (zuchon #29: the cube was already moving before pad release, so any look-back reaching before trial start pays it). Replay: zuchon 5 → 7 paid, anaconda 1 → 3, all current successes kept (lowest 0.0072, the fast tap #16), all failed trials without movement ≤ 0.0040; noise max 0.0045 over 5167 paw-on-pad 5 s windows from 8 sessions (20–22 Oct), so threshold / noise max = 1.34 (current rule 1.20); 0 false alarms in 20293 overlapping windows. Across all 118 min of paw-on-pad data the rule crossed 3 times, all real cube movements (2 of them also near or above the current threshold). Limits: a paw resting on the cube without moving it cannot be detected (zuchon #5 possibly); 1 kHz equivalence assumes one middle-loop iteration per sample. Do NOT load a 0.006 file on the current VI (raw noise is above 0.006, every trial would pay). VI steps (guided one at a time): Mean PtByPt (length 10) on push and lateral feeding both the subtraction and the Feedback Node; Feedback Node delay 50 → 200; in Case 3 AND the velocity term with (Tick Count −trail start) ≥ 200. Status 22 Oct (user, on the rig PC): Mean PtByPt (10) on push and lateral built and idlevelocityconfirmed lower; Feedback Node delay set to 200. The 200 ms start gate (step 3) was deliberately SKIPPED, so a push-signal jump just before pad release is inside the look-back and can pay without a touch (zuchon #29: paw on pad, cube looked still on both cameras, push drifted down 6 mV over 0.5 s then jumped back up 10 mV at release; ungated peak 0.0115). Replay: ungated, this was the only extra false reward among 73 trials in the two sessions. Not yet re-tested on the bench after the edits. Filetraining/02_micro_touch_velocity_200ms_thr0p006_smooth.txt(500 rows, 28 columns checked, identical to the 0.012 file except column 28 = 0.006; ITI is still 1500 ms, see unpaid successes in Known bugs). Original plan text: a new 28-column file with 0.006 and a bench test (idlevelocitybelow 0.0045, finger taps and slow pushes pay, table knock). Analysis scripts and figure are outside the repo (not committed). - Session → parameter file map (16–23 Oct; inferred 24 Oct from EVENTS/joystick data: water ms, ITI,
move time, pad-hold length, push at trial arm/success, velocity replay; plus file git history and chat notes). All files are inparameters/training/except the bench preset. 16 Oct: anaconda_102_micro_push_day1_restpad; zuchon_1 same (0 trials, rest 2.44 V below home); zuchon_2 (10 s false start, probable) and zuchon_3_home2p44; blackmamba_1_home2p475(restored). 19 Oct: anaconda_1 (4 s, probable), anaconda_2 and zuchon_1 (1.4 min, probable)parameters/push_bench_full-system.txt(5000 ms ITI/move time, start box 2.46–2.54; most likely still loaded after the release-clause bench test; 0 rewards); zuchon_2_home2p44-2p49_hold100_end2p35(restored); zuchon_3 and blackmamba_1_home2p44-2p49_hold100_end2p40(restored). 20 Oct: anaconda_1 and blackmamba_1_home2p44-2p49(current, hold 50); anaconda_2, zuchon_1, blackmamba_201_pad_touch_50ms_release. 21 Oct: anaconda_101_pad_touch_100ms_release; zuchon_1, blackmamba_101_pad_touch_200ms_release. 22 Oct: anaconda_1, zuchon_102_micro_touch_velocity_200ms_thr0p012(replay exact); blackmamba_1_thr0p006_smooth(timeline; 1 reward, replay cannot separate 0.006 from 0.012); blackmamba_2_thr0p006_smooth_1hr. 23 Oct: all three_thr0p006_smooth_1hr(no timeouts, rewards up to 12.7 min after release). The offline velocity replay matches the VI exactly only before the smoothing edit; after it, the VI's velocity differs from a per-sample Mean(10)/lag-200 replay (check the actual loop rate/wiring before relying on replays). LABVIEW timing files exist (24 Oct) for every 16–23 Oct session with rewards; the 7 sessions without rewards (zuchon 16 Oct_1/_2, anaconda 19 Oct_1/_2, zuchon 19 Oct_1/_2, blackmamba 20 Oct_1) get none by design. Restored files are byte-identical copies from git (34ea229,6145cb0,9d24808). - Zuchon mixed touch/cube file (29 Oct; written, not bench-tested; run with zuchon 29 Oct #1 and 30 Oct #2, where the VI ran the wrong rows, see "Parameter-row mapping"). From the zuchon analysis of 26-28 Oct (PushTask_Analysis
notebooks/zuchon_shaping_week26-29Oct.ipynb): hold and release are learned (28 Oct #1 touch file: 48 rewards in 11.5 min), but on the 1 h velocity file zuchon earned only 6-8 rewards per session; release-to-cube coupling fell to chance after whole touch-only sessions. Recommendation: alternate the two trial types within one file. Fileparameters/training/02_mixed_touch1_cube1_100ms_thr0p006_smooth_5s_1p5ul_3ul.txt(500 rows, 28 columns checked): odd rows touch-and-release (boxes 0-5 V,vel threshold0, 58 ms water ~1.5 µl, interpolated, not weighed); even rows cube (home push 2.43-2.53, end box 0-1 V,vel threshold0.006,move time5000, 100 ms water ~3 µl, measured 3.2 µl); all rowshome TO100, ITI 2000 (avoids the unpaid-success bug). Replay on last week's behaviour: 14 / 7 / 20 / 38 rewards vs 9 / 6 / 7 / 8 under the 1 h rule. Before a mouse: bench-test that per-row boxes,vel thresholdandwatertimeare applied (finger lift pays on odd rows; on even rows lift alone times out after 5 s, lift + tap pays the longer pulse) and that a timeout advances to the next row. Next steps by the notebook: 1:2, then 1:3, then cube rows only, when P(cube movement within 2 s of release) is clearly above chance; then shorten the cube window toward 2-3 s. Open questions from the notebook: why the push channel was flat on 28 Oct #1 (cube removed?), and the 28 Oct #2 video (does the paw go toward the cube after a release?). - Zuchon pad + velocity file (30 Oct; written, NOT bench-tested, not run). parameters/training/02_pad_velocity_100ms_thr0p006_smooth_5s_1p5ul_iti1p5.txt (500 identical rows, 28 columns checked): the cube rows of the 29 Oct mixed file only (no touch-only trials): home TO 100, home push 2.43-2.53, end push 0-1 V, �el threshold 0.006, move time 5000, 58 ms water (~1.5 µl, interpolated, not weighed), ITI 1500, consumption 1000 ms. Next: bench check, then run; compare rewards per session with the mixed file.
- Anaconda distance-based file (30 Oct; written, NOT bench-tested; first run 30 Oct #1 failed on the home-box typo below, rerun pending). From the
session_explorationnotebook of PushTask_Analysis (anaconda 29 Oct #1, 199 velocity-paid rewards in 49 min, peak push median 31 mV; rewards kept at a minimum distance of 10 / 20 / 30 / 40 / 50 mV: 92 / 71 / 52 / 45 / 35 %). Fileparameters/training/03_push_distance_200ms_20mV_1s_1p5ul.txt(500 identical rows, 28 columns checked):vel threshold0,home TO200, home push 2.438-2.482 V (was mistyped 4.438-4.482 V: sessionanaconda_20261030_1got 0 trial starts in 2.2 min; push rest that session median 2.441 V, 89 % of pad-on time inside 2.438-2.482), end push 0-2.41 V (no overlap with home),end TO0,move time1000, ITI 1500, 58 ms water (~1.5 µl, interpolated; weigh), consumption 1000 ms (water loop ≈ 1.1 s < ITI 1.5 s, addresses the busy-loop cause of unpaid successes; check in EVENTS that every code 4 has codes 10-18), other columns as the velocity files. Only velocity-paid pushes were analysed, so the real reward rate under the new rule (and within a 1 s window) is unknown. Before a session: measure rest and re-centre home / bound; bench check that a ~25 mV push pays and a touch alone does not. Next: if anaconda keeps ≥ 15 rewards / 10 min, step to 30 mV, then 40 mV. - Broad-zone file (4 Nov; written, NOT bench-tested, not run).
parameters/training/04_broad_zone_4-8mm_hold50ms_home2p46-2p51_1s_1p5ul.txt(500 identical rows, 28 columns checked): copy of03_push_distance_200ms_home2p46-2p51_end2p40_1s_1p5ul.txtexcept end push box 2.235-2.365 V (= 8 / 4 mm from the 3 Nov volts-mm calibration, where 4 mm = 2.365 V and 8 mm = 2.235 V; the calibration home was ~2.47 V, so re-centre if the rest voltage differs) andend TO(column 14) 50 ms. Pushing past 8 mm (below 2.235 V) is outside the zone. Next: measure rest voltage, bench-check that the zone pays only inside 4-8 mm. - Narrow-zone file (6 Nov; written, NOT bench-tested, not run; for anaconda).
parameters/training/05_narrow_zone_4p5-6p5mm_hold50ms_home2p46-2p51_1s_1p5ul.txt(500 identical rows, 31 columns checked; columns 29-31 appended asreward on touch0,joystick only0,repeat trials1, so a timed-out trial repeats the same row; needs the repeat-trials VI edit, which is not built/tested): copy of the04_broad_zone_4-8mm_...file except end push box 2.284-2.349 V (4.5 / 6.5 mm, linear interpolation of the 4 mm = 2.365 V and 8 mm = 2.235 V calibration points, 0.0325 V/mm, rounded to mV). Same caveat: re-centre if the rest voltage differs from the ~2.47 V calibration home. Next: bench-check the zone. - 2 s velocity file (4 Nov; written, NOT bench-tested, not run).
parameters/training/02_micro_touch_velocity_100ms_thr0p006_smooth_2s_home2p46-2p51_1p5ul.txt(500 identical rows, 28 columns checked): identical to the_3s_file exceptmove time(column 19) 3000 -> 2000 ms. - Next stage plan (20 Oct): hold + easiest micro-push. Pad hold goes 50 → 100 → 200 ms over the next days (touch-and-release sessions); the micro-push stage should reward any push above noise. Current day-one file leaves a dead band (home 2.44–2.49, success < 2.40: a cube at 2.47 V needs ~70 mV, ~2 mm). Simplest: per-session file from the measured rest voltage r (home r ± 0.010, success < r − 0.020, keep ≥ 10 mV gap between home lower bound and success bound, optional
end TO10–20 ms,move time5000); measure real 1 kHz noise from today's touch-only joystick files first (99.9th percentile; the ±0.035 V incalibration/joystick-volts-mm.mdis the old live-graph value). Robust option after the release-clause bench test: Case 5 stores the push value at release as a baseline and Case 3 rewards when push < baseline − Δ (new front-panel control, e.g. 0.015 V) OR the end box; Δ = 5 V recovers the old behaviour. Drop the pad hold to 50–100 ms when the push requirement is added. - Success bound raised (19 Oct, committed 20 Oct);
home TOback to 50 ms (20 Oct). In02_micro_push_day1_restpad_home2p44-2p49.txtthe end box is now 0.00–2.40 V (column 12; was 2.35; 250 rows, 27 columns checked). Home stays 2.44–2.49 V,move time3000 ms,home TO50 ms (column 4, all 250 rows; 27 columns checked). Reason: mousezuchonrests the paw on the cube and settles at 2.37–2.40 V, so 2.35 V was just out of reach; a resting paw alone may now succeed after a trial start, so watch whether it is a deliberate push. - Bench presets (no animal):
parameters/push_bench_full-system.txt(20 rows; home/start matchtraining/07_expert.txt; success 0.00–2.22 V, no end dwell; 50 ms cue, 500 ms reward delay, spout 0→3 V, 185 ms water, 1500 ms consumption; set Session start spout to 0 V so motion is visible;mag=0; never use to energise the Ledex/PVA) andpush_bench_full-system_nocue.txt(same, cue 0 ms; if a click is heard disconnect the buzzer from P0.1). Procedure inparameters/README.md.parameters/push_bench_wide-zone.txtis the older hand-test file. - Repo layout (cleaned 16 Oct). Docs describe the capacitive pad with no FSR history; old FSR bench logs are in
archive/FSR_rest_pad_history.mdand the old fixedledex-holder/is inarchive/(archive/is for history worth keeping). Original 20 mm cube and original (arched) 1D-axis constrainer STEP/STL moved toarchive/(17 Oct); the generator scriptsmake_cube_11p5.pyandmake_ledex_window.pywere deleted (the final STEP/STL models are the record); emptymechanical/laser-cutremoved. Shaping fallbacks renamedparameters/training/01_pad_contact_shaping_50ms.txt/_250ms.txt(home TO50 / 250 ms; untested with the capacitive pad). The docs website (Jupyter Book from the upstream repo:_config.yml,_toc.yml,requirements.txt,.github/workflows/publish-book.yml,docs/images/logo.png) is kept;_toc.ymllists all current docs (check on GitHub that the publish Action/Pages works). Upstream boilerplate kept (LICENSE,CONTRIBUTING.md,CONDUCT.md,media/output.png). Washer-simulation figures and data are kept on purpose (they show why the washer target was rejected). Maintenance pass 5 Nov: the 17 Oct (10 mm) and 28 Oct (14.1 mm) narrow-cube revisions moved toarchive/joystick-handle-cube/, only the newest (..._h15p1_d3p3_dp4p2, 15.1 mm, 3.3 mm × 4.2 mm holes) plusmake_handle_cube.pystay inmechanical/joystick-handle-cube/;rig_geometry.pyCUBE_STLand the script's--check-oldnow point at the archived files (geometry output checked identical;results/rig/stackup.csvregenerated, it was stale since 15 Oct: slot limit 35.85 mm, 15 mm tilt 7.43°); 6.DS_Storeand 5__pycache__/*.pycfiles that were still tracked are now untracked (already in.gitignore); empty, unreferencedtraining/02_micro_touch_velocity_200ms_thr0p006_smooth_1hr_1ul.txt(0 bytes since it was added) deleted; README,RIG_INVENTORY.md,MAGNET_COMMISSIONING.md,EVENTS_LOGGER_PLAN.md,parameters/README.md,calibration/andmechanical/READMEs brought in line with this plan; order/price details removed from the public docs. All parameter files validated (consistent 27–30 columns per file,mag= 0 everywhere).mechanical/has one README (parts table and notes) plus the Ledex holder README; per-folder READMEs removed and the mechanical/3d-print level flattened into mechanical/ on 18 Oct; the 38 mm bar handle moved toarchive/joystick-handle-bar/. Joystick component model renamedmechanical/TS-1D1S00A-1294_stripped/(filesTS-1D1S00A-1294_stripped.step/.stl;rig_geometry.pyupdated). - Repo: the rig PC was re-cloned on 11 Oct because
origin/masterhad its history rewritten (force-pushed twice); localmasterwas reset toorigin/masterat5302f6d. Alwaysgit fetch originand checkgit statusbefore committing; if it says ahead and behind, do not merge, ask first. After the 5 Nov maintenance pass, pull on the rig PC before the next LabVIEW session (files moved, nothing the VI loads was renamed).
- Joystick file: 5 doubles per 1 kHz sample. TRIAL/REWARD files unchanged. Frame count is software-polled once per loop, not hardware-aligned.
- EVENTS logger (done, bench-validated,
f9a363c; plan inEVENTS_LOGGER_PLAN.md): fileEVENTS_<name>, big-endian float64, 4 per record[code, sample_index, unix_time_s, value]. Codes: 1 start, 2 state change, 3 trial start (start zone, Case 2; value = 1-based parameter row since the row-mapping fix, 0 in older sessions), 6 paw release (Case 5), 7 go cue burst, 8 touch-mode pass (Case 5, see "Reward on touch"), 9 joystick-only arm (Case 5, built 5 Nov, bench test pending, see "Joystick-only sessions"), 4 success (Case 4), 5 timeout, 10–18 water-loop steps (13 = extend V, 15 = water on with ms, 18 = retract V), 20/21 magnet on/off, 90 heartbeat (1 s), 99 stop. Named queuepush events, reentrantLog Event.vi, one writer loop, globalsample count(reset to 0 before code 1). Known: code 99 sample is ~0.3 s stale (excluded from fits). REWARD col 0 matches code 15, not code 4. Pending tests: camera sync run, stop from inside a reward sequence. - Auto timing file:
scripts/run_timing.batis called by System Exec at Stop after the EVENTS Close/Release (commit92c1cc5) and writesTIMESTAMPS_LABVIEW_<name>.npyviascripts/make_labview_timing.py(numpy only; refuses on files from different runs, empty REWARD, >5% outliers, drift ≥100 ms). Output intiming_log.txt(overwritten each session, so only the last run is kept). The trial-count check allows extra EVENTS trial starts (the last 1–2 TRIAL rows can be missing); analysis usesEVENTS(code 3 trial start, code 15 reward), while the VI still writes TRIAL/REWARD. Merge Errors for the other file closes was skipped; revisit only if "Missing session file" errors appear. - Video timestamps (
TIMESTAMPS_VIDEO_*.npy) are absolute Unix float64 at ~100 Hz; PC Unix time is the shared clock. File creation times are not session markers.
- Trial: forepaw on rest pad AND object in home → trial starts → push forward into target band (held for
end TO) → immediate auditory cue (50 ms) → reward delay → spout extends → water → spout retracts. Overshoot is not an abort; leaving the band resets the dwell; trial ends atmove time. - Session blocks (later): Baseline (
mag=0), Random perturbation, Fixed perturbation, Washout. Perturbation = axial resistance along the push axis (Ledex mounted on the home side, attraction opposes the push), pre-scriptedmagper row, force magnitudes must be varied (proportional driver). - Training progression (criterion on two consecutive days, change one thing at a time, ~3–6 weeks): 1) head-fix habituation with cue→water (short interval); 2) micro-push discovery; 3) proximal broad zone; 4) zone translation; 5) zone refinement; 6) delay 0/250/500/1000 ms; 7) expert stabilisation (≥80% success); 8) zero-force shadow sessions; 9) resistance pilot (separate mice), then main experiment. Optional rest-only stage as fallback.
- Parameter files: one file per stage or mouse in
parameters/training/, bench presets inparameters/(27 base columns plus optional 28vel threshold, 29reward on touch, 30joystick only, 31repeat trials; seeparameters/README.md). Loader reads cases 0–30; missing optional columns load as 0. Columns (0-based): 0,2homepush bounds (named X); 1,3homelateral (named Y); 4home TO; 5–8 legacystartbox; 9start TO(keep 0); 10,12endpush bounds; 11,13endlateral; 14end TO; 15mag; 16magtime; 17watertime(130/185/240 ms → 4/6/8 µl); 18timeout(Case 0 ITI); 19move time; 20light(0); 21reward delay; 22cue duration; 23spout extend; 24spout settle; 25consumption; 26spout retract. White/push values go in the columns named X; there is no ×2 onhome X2. Do not reuse the old pull files' end voltages ormagtime=100. - Calibrate before final numbers: volts-to-mm done 3 Nov for 0–8 mm (0.0295 V/mm,
calibration/joystick-volts-mm.md; 10 mV ≈ 0.34 mm, so the 20 mV distance file asks ~0.7 mm andvel threshold0.006 V ≈ 0.2 mm), joystick noise, water volume (calibration/water-volume.md), spout settle, spring return. - A fixed-spout 500 ms ITI with a 1.5 s consumption loop needs a rapid-success bench check (one cue and one water pulse per success).
- Perturbation notes: original Mathis kick was lateral at trial start; ours is sustained axial resistance. Minimal option
magtime ≈ move time; cleaner is holdingmaguntil a trial-end occurrence (Case 4 or Case 3 timeout) and a ~100 ms movement-onset delay. Do not reuse oldmagtime=100or the 2.2 V value; force-calibrate with a gauge/load cell and check heating. - Earlier plan (superseded by the Case 5 release clause above; kept for the replay numbers): start the trial (and
move time) at pad release. Use the existing Case 1 gate: Case 0 (pad + home heldhome TO, ITI elapsed) → Case 1 waits while the paw is on the pad; pad released AND object in the start box (= home box,start TO0) → Case 2 (trial start, code 3, magnet occurrence); pad released but object out of the box → Case 0. No parameter-file change. Code 2 (state 1) then marks "armed", code 3 marks release (~50–60 ms after the real lift-off). Replaying the 16 Oct data through this rule: blackmamba 83 trials / 22 rewards (27 %; 25 and 31 rewards withmove time5 / 8 s) instead of 250 / 19; zuchon 21 / 6; anaconda 28 / 15 (its pushes with the paw on the pad no longer pay). Separate ITIs after success vs timeout were considered and not needed for this. Step 1 (pending): user inspects Case 1 (what it does when its condition is false; wherepaw on rest padis computed). DataJoint: trial start means pad release from that session on. - Future LabVIEW work: habituation mode (cue→delay→water without Case 4), rest-pad hysteresis, movement-onset-triggered or held
mag, session metadata, one shared parameter typedef (the water-loop and loader clusters had mismatched element order; fixed, FP captions swapped for reward delay and spout retract).
VI architecture (Push Behaviour_MCHALABI.vi + avg joystick and frame trig lick3.vi, frame counter.vi, PushTask Globals.vi, Log Event.vi)
- State machine in the middle While loop (state in a shift register, initial 0): Case 0 gate (object in home ROI AND rest pad
ai2> threshold, heldhome TO, ANDtimeoutelapsed, ANDrearmed; pad terms bypassed whenjoystick only), sets the go-cue occurrence (not in joystick-only mode) → Case 5 waits for paw release (passes at once in touch or joystick-only mode), stampstrail startand logs code 6 / 8 / 9 → Case 1 start ROI, Case 2 stamp trial start and set the magnet occurrence, Case 3 movement window (timeout → Case 0; end ROI heldend TO, or velocity abovevel threshold, → Case 4), Case 4 set the reward occurrence. - Parallel loops besides the water and magnet loops below: go-cue loop (occurrence → burst on
GoCue_Dev1, code 7) and the EVENTS writer loop (named queuepush events, heartbeat). - Water loop (occurrence-driven flat sequence): cue HIGH → cue wait → cue LOW → reward delay → spout extend → settle → water HIGH →
watertime→ water LOW → consumption → spout retract. - Magnet loop: occurrence → AO0
magformagtime(plus laser counter pulse, keyed off) → reset 0. - Startup writes cue LOW, water LOW, magnet 0 V, spout to
Session start spout (V); normal Stop propagates throughstop all; Stop tests from idle, reward delay and extended spout passed. - Rest-pad threshold control and
paw on rest padBoolean sit before thehome TOhold. Home gate and rest pad share one timer; ROI X-in-range AND Y-in-range per row. - Row selection (since the 5 Nov row-mapping fix):
Trial #(0-based index of the next row) is written only in Case 5 True, which copies it totrial rowand increments it; Cases 0/5 readTrial #, Cases 1/3 and the magnet and water loops readtrial row; code 3 value =trial row+ 1. Planned: a Case 3 timeout write forrepeat trials. The magnet loop can miss a trial-start occurrence (zuchon 16 Oct after a 328 s pause; 29 Oct #1 trial 98 with the loop idle), which also drops a TRIAL row and codes 20/21.
- Joystick: APEM TS-1D1S00A-1294 Hall thumbstick base (castle cap removed; printed shaft screwed onto the lever stub) in the printed component holder, 89 mm shaft + interchangeable tip handle: narrow rounded cube, 11.5 × 11.5 mm footprint, shaft in the front hole (replaced the 20 mm-wide cube used in session 1). Revisions: 10 mm tall (17 Oct), 14.1 mm (28 Oct), 15.1 mm with 3.3 mm × 4.2 mm holes (5 Nov, newest, not yet printed; the only one left in
mechanical/joystick-handle-cube/, older ones inarchive/). Which revision is on the rig is not recorded; the force/geometry models still use the 10 mm cube. 1D-axis constrainer: the 16 mm window revision is on the rig (1 Nov, archived in the repo); the wide-window revision is CAD only. Adjustable Ledex holder, link collar and link bar mounted 1 Nov. The bar handle is archived. CAD (STEP + STL) inmechanical/. Return-spring stiffness still to be measured (gram scale). - Magnet coupling: plunger linked to the shaft (see Ledex "Plunger link"). The steel washers (SFS M4
111952, M3132845) are no longer the target. - Ledex
195224-230: 12 V, 7 W, 20.7 Ω, ~0.58 A nominal, pull type, mounted axially opposing the push. - Water: bubble-free priming before every session; calibrated pulse volumes in
calibration/water-volume.md. - Actuonix spout: bench-tested extend/settle/water/retract; mouse-relative extend voltage and settle time still to calibrate.
- Design authority: Weekly Update 29/06/26 (approved by the PI). Upstream: Mathis et al. 2017 (2D joystick pull).
- Training precedent: Mathis 2017; Wagner 2020 (manipulandum troubleshooting); Roth lever-push and Linares-García 2025 (push less natural than pull). Rest pad defines a pre-contact start state, not identical posture.
- The untouched
Pull Behavior_march2020.vihierarchy stays on the rig PC as the fallback. Keep the dated working archive through at least the first successful animal pilot. - Repo docs:
README.md,RIG_INVENTORY.md,MAGNET_COMMISSIONING.md,EVENTS_LOGGER_PLAN.md,parameters/README.md,calibration/,simulations/ledex_femm/README.md. - Rig-PC Python is 3.6.4 (system numpy 1.14, no matplotlib; do not upgrade it,
make_labview_timing.pydepends on it; use a throwaway venv for plotting); Git for Windows 2.55 and FEMM 4.2 (C:\femm42) installed. Mac: FEMM 4.2 under Wine (prefix~/.wine-femm, install steps in the ledex_femm README).