Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2218716
feat(journal): a temporary encoder offset is a transaction — journal,…
claude Jul 12, 2026
b1a442d
feat(limits): the four-verdict record — WALL / TORQUE-LIMITED / EDGE …
OriNachum Jul 12, 2026
1b9431a
test: two guard rails — the seam arithmetic, and the addr-9/11 write …
OriNachum Jul 12, 2026
5720bb9
feat(ticks): RAW-tick persistence — one conversion boundary between t…
OriNachum Jul 12, 2026
7001075
memory: the rolling frame + the two rulings behind the arm-limits spe…
OriNachum Jul 12, 2026
a31cea1
merge(t1): wave 0 of the arm-limits build
OriNachum Jul 12, 2026
fe32b14
merge(t2): wave 0 of the arm-limits build
OriNachum Jul 12, 2026
f5577e9
merge(t3): wave 0 of the arm-limits build
OriNachum Jul 12, 2026
0441f10
merge(t4): wave 0 of the arm-limits build
OriNachum Jul 12, 2026
0366c89
feat(classify): BOUNDED, CONTINUOUS, or honestly UNDETERMINED — from …
OriNachum Jul 12, 2026
2f2e97b
feat(rolling-frame): roll the seam ahead of the joint, so travel is u…
OriNachum Jul 12, 2026
c3746bc
merge(t5): wave 1 of the arm-limits build
OriNachum Jul 12, 2026
96e391d
merge(t7): wave 1 of the arm-limits build
OriNachum Jul 12, 2026
10edaa5
feat(rezero): derive an arc from a live measurement, and retract #43'…
OriNachum Jul 12, 2026
ab02ce9
merge(t8): measured arcs + retract the false rezero claim
OriNachum Jul 12, 2026
983aa45
feat(probe): drive ONE joint outward until it learns WHY it stopped (t6)
OriNachum Jul 12, 2026
db6fa68
merge(t6): the creep — four verdicts from a distance, not a state
OriNachum Jul 12, 2026
2115e51
feat(limits): the verb that measures the arm — and puts it back (t9 +…
OriNachum Jul 12, 2026
6418c4a
merge(t9+t11): the 'arm limits' verb — measure-only, under torque_gua…
OriNachum Jul 12, 2026
a357be1
feat(limits): `arm limits --commit` — the sweep is the arbiter, not t…
OriNachum Jul 12, 2026
6cdcfd5
merge(t10): the commit path — the sweep is the arbiter; close every s…
OriNachum Jul 12, 2026
8defb1d
feat: the arm measures its own joint travel — `arm limits` (v0.23.0)
OriNachum Jul 12, 2026
ba825fb
fix: wrist_roll is BOUNDED — an unfirable threshold hid two real wall…
OriNachum Jul 13, 2026
9eb9c14
feat: every contact threshold is now measured, not assumed (v0.24.0)
OriNachum Jul 13, 2026
8287743
fix: hardware puts a floor under the pressing-excess constant (v0.24.1)
OriNachum Jul 13, 2026
70db979
chore: leave the arm safe and idle after the wrist_roll compliance run
OriNachum Jul 13, 2026
aa2c938
docs: RESUME-HERE — cold-start pointer into the joint-limits programm…
OriNachum Jul 13, 2026
10f4108
fix: the STOP path let go of the report but not the arm (v0.24.2)
OriNachum Jul 13, 2026
97f0f49
test: only the call under test may throw inside the raises block
OriNachum Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eidetic/memory/arm101-cli__public.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
{"id": "rezero-proven-on-hardware-2026-07-12", "hash": "44eb11b90e77d60071dd52a29fc154808c74a7f85aac3bf462ae0be28e1a2703", "content": "SETTLED ON HARDWARE (2026-07-12, follower /dev/ttyACM1, human present): THE ENCODER RE-ZERO WORKS, and the spike`s open caveat is CLOSED BY MEASUREMENT.\n\nTHE BIG RESULT \u2014 the STS3215 DOES reduce the corrected position MODULO 4096, so the offset genuinely RELOCATES the seam (it does not merely relabel positions). Proven by a torque-off hand sweep of elbow_flex, before/after, same joint same motion:\n Ofs=0 -> monotonic FALSE, 1 discontinuity, span 4093 (the wrap, captured)\n Ofs=1073 -> monotonic TRUE, 0 discontinuities, span 2196, largest jump 28 ticks\nIssue #35 is FIXED for elbow_flex on the physical arm. The pessimistic reading (plain signed subtraction, seam stays pinned) is REFUTED.\n\nFACTORY OFFSET IS 85, NOT 0. All six servos ship with Ofs=+85 at addr 31 (uniform across six joints => a vendor default, not a calibration). The t5 spike ASSUMED factory 0. CONSEQUENCE: every tick ever recorded on this arm was measured in the 85-shifted frame. This is WHY elbow_flex wraps \u2014 the reported seam sits where Actual==Ofs, i.e. at raw 85, which is BELOW the unreachable arc`s lower edge (raw 211) and therefore INSIDE the joint`s travel.\n\nSEMANTICS CONFIRMED TWICE, reversibly: writing Ofs 85->185 dropped reported position by EXACTLY 100; zeroing 85->0 raised it by EXACTLY 85. So Present_Position = Actual - Ofs.\n\nELBOW_FLEX`S FAR WALL, MEASURED FOR THE FIRST TIME (nothing could see across the seam before): travel spans 2196 ticks, reading 1034..3230 in the corrected frame at Ofs=1073. Converting to RAW (raw = corrected + 1073 mod 4096): raw travel = [2107,4095] U [0,207], so the TRUE RAW UNREACHABLE ARC = (207, 2107), width 1900, midpoint 1157. The shipped table said (126,2020)/midpoint 1073 \u2014 those were REPORTED-frame ticks in the 85 frame. 1073 lands inside (207,2107) with 866/1034 margin, so the shipped code WORKED BY LUCK, not by construction.\n\nALSO VALIDATED LIVE: PR #38`s torque_guard fired on real hardware \u2014 a failing `arm rezero` released motor 3 on its way out (`Torque released on motors 3 after an abnormal exit`). And t8`s false-pass guards rejected THREE sweeps as INCONCLUSIVE (0, 0 and 376 ticks of coverage) before the real one \u2014 a naive `no discontinuity seen => PASS` would have declared victory on an empty sweep with the arm untouched.\n\nSTILL UNPROVEN: PERSISTENCE. The offset reads back as 1073 but has NOT survived a power cycle yet \u2014 PR #21 exists because exactly this read back fine and reverted on power-up. Cut and restore the 12V BUS POWER (not USB) and re-read addr 31.\n\nGOTCHA: a read issued immediately after an EEPROM write can return GARBAGE \u2014 a read_position ~0.2s after write_offset returned 0 while the servo genuinely held 3387 (re-reads were stable and correct). A plausible-looking wrong value is worse than an error.", "scope": {"name": "arm101-cli", "visibility": "public"}, "metadata": {"type": "note", "record_metadata": {"source": "hardware-run", "area": "arm101/hardware/rezero.py", "issue": "35", "port": "/dev/ttyACM1", "version": "0.21.0", "status": "seam-evicted-persistence-pending"}, "created": "2026-07-12T12:01:22.414446+00:00", "last_recall": null, "recall_count": 0, "links": ["sts3215-offset-register-facts", "seam-verification-false-pass-traps", "wrist-roll-cannot-be-re-zeroed", "encoder-wrap-two-joints"], "supersedes": null, "lifecycle": "active", "added_by": null}}
{"id": "arm-measures-its-own-walls", "hash": "bc99af74a9d79675aa2832f137123d0c5a91c6102345f186f0841ec35c96bf70", "content": "THE ARM MEASURES ITS OWN WALLS BETTER THAN A HUMAN DOES (2026-07-12, follower, PR #41). Once elbow_flex`s axis was linear (post re-zero), gentle_move was driven PAST the known travel and left to find each wall by feel \u2014 creep under torque, watch present_load, stop on contact, back off, hold. The blind-person-in-a-room primitive; the same one `arm explore` uses.\n\n arm flex elbow_flex --to 900 --gentle -> contacted 988, load 500 (SATURATED)\n arm flex elbow_flex --to 3400 --gentle -> contacted 3274, load 500 (SATURATED)\n\nTHE MACHINE BEAT THE HAND ON BOTH SIDES. Hand sweeps gave raw walls 218 / 2107 (and varied 206..218 depending on push force \u2014 a human stops when it FEELS firm). The arm gave 251 / 2061 \u2014 further out AND repeatable, because it presses to a fixed load threshold every time. Load SATURATING at the 500 torque cap is the signature of a real wall, not of an operator`s judgement. LESSON: for anything the arm can measure about its own body, let the arm measure it.\n\nTHE SECOND MOVE IS THE PROOF OF THE WHOLE RE-ZERO: commanding corrected 3400 CROSSES the raw 4095->0 boundary, and in the corrected frame it is a plain linear climb that converged. That exact command would have rotated the elbow THE LONG WAY ROUND into a wall before the re-zero.\n\nDERIVED CONSTANT DISCIPLINE (the durable lesson): the arc table exists to be RE-MEASURED, but ~125 arc-coupled literals were hard-coded across two test files, so re-measuring it broke 34 tests. Tests now DERIVE every expectation from arm_spec (REZERO_ARCS, .midpoint, .evicts, _ARC_MARGIN_TICKS). ACID TEST: change the margin or either wall -> suite stays green. Same rule applied to prose: docstrings and the explain catalog no longer quote ticks AT ALL \u2014 a doc that names a measurement is a doc that goes stale, and `explain arm rezero` prints to whoever is standing at the arm.\n\nARC MARGIN IS LOAD-BEARING: a first cut declared the arc AT the extremes of one hand sweep and FALSE-REFUSED within minutes (the joint came to rest 11 ticks past an edge the operator had simply stopped short of). Declare the arc as a STRICT SUBSET of the unreachable region, inset on both sides. Conservative in both directions: cannot false-refuse a legal position, and cannot claim a tick the joint can actually reach. NOTE the operator had to move the gripper aside `or we would hit the table` \u2014 so a wall may be ENVIRONMENTAL, not mechanical, which makes true travel WIDER and the true arc NARROWER. Margin absorbs that too. This is issue #34`s `the table is the wall` arriving in a new place.\n\nOPERATOR INSIGHT WORTH KEEPING: `there is the max of each joint, but also the max of a certain setup/joined movement`. That IS issue #34 \u2014 single-joint range vs COMBINATION reachability \u2014 and it remains unanswered until the grid lands.", "scope": {"name": "arm101-cli", "visibility": "public"}, "metadata": {"type": "note", "record_metadata": {"source": "hardware-run", "area": "arm101/hardware/arm_spec.py", "issue": "35", "pr": "41", "version": "0.22.0"}, "created": "2026-07-12T13:14:07.753531+00:00", "last_recall": null, "recall_count": 0, "links": ["rezero-proven-on-hardware-2026-07-12", "sts3215-offset-register-facts", "arm-explore-grid-is-structurally-wrong"], "supersedes": null, "lifecycle": "active", "added_by": null}}
{"id": "the-software-bound-hides-the-seam-on-every-joint", "hash": "3f7f957c0fd7eca8a61435fd4e878600fe6dbcba06997f0846ef3e220f1e8bed", "content": "MAJOR FINDING (2026-07-12, follower, code-driven limit probe of all six joints). ISSUE #35 IS NOT A TWO-JOINT PROBLEM \u2014 IT IS POTENTIALLY A SIX-JOINT PROBLEM, and the software bound has been HIDING it.\n\nTHE STRUCTURAL REASON: every joint except elbow_flex still carries the FACTORY offset Ofs=85. The commandable bound is reported tick 4095. With Ofs=85 that is RAW 84 \u2014 exactly ONE TICK BELOW THE SEAM, which lives at raw == Ofs == 85. So for every factory-offset joint, THE SOFTWARE BOUND LANDS PRECISELY ON THE SEAM. The arm has been reporting the seam as its boundary the whole time.\n\nEVIDENCE \u2014 gentle_move probed each joint to both ends of its permitted range in one pose. Three joints reached the bound with NO CONTACT (still free), stopping this close to the seam in raw ticks:\n shoulder_lift : reported 4094 -> raw 83 (2 ticks from the seam) NO WALL\n gripper : reported 4093 -> raw 82 (3 ticks from the seam) NO WALL\n shoulder_pan : reported 4085 -> raw 74 (11 ticks from the seam) NO WALL\nNo contact means they could still have kept going \u2014 their remaining travel lies ACROSS the seam. elbow_flex is the ONLY honest row (real WALLS at both ends, 983 and 3267, with 829 ticks of clearance from its seam) precisely because its seam was moved out of its travel by the re-zero.\n\nTHIS EXPLAINS WHY THE REACHABILITY MAP WAS GARBAGE \u2014 not just the grid (#34), but the measurements feeding it.\n\nCHICKEN-AND-EGG: to re-zero a joint you need its unreachable arc, but you cannot measure the arc until you can see PAST the seam. WAY OUT (already proven on elbow_flex): temporarily shift the offset to move the seam elsewhere, probe freely to find the TRUE travel, then compute the proper offset from that.\n\nOPERATOR`S FRAMING, adopted: MECHANICAL limit = the WIDEST envelope ever recorded across configurations (an obstacle can only make a range smaller, never larger, so the max over poses converges on the mechanical truth). ENVIRONMENTAL limit = whatever is narrower in a given pose. Mechanical belongs in arm_spec (intrinsic, portable); environmental belongs in the map (pose-dependent, and NOT in the servo`s EEPROM).\n\nALSO PROVEN: elbow_flex`s walls are MECHANICAL, not the table. Probed in 3 poses (shoulder_lift 3354 vs 3779, wrist_flex 3963 vs 1366): wall at 988/986/984 low and 3274/3278 high \u2014 moves by <=4 ticks while other joints move by hundreds. Intrinsic.\n\nGOTCHA on ticks/second from `arm profile`: a SHORT probe underestimates travel rate badly, because the ~155ms motion-onset latency dominates the timing. First run (221-tick probes) reported 105-228 ticks/s and the rate FELL as speed rose \u2014 a measurement artifact, not physics. Rate must be measured over a LONG travel.\n\nRISK r2 IS REFUTED: contact detection still fired at EVERY speed from 150 to 1600 on elbow_flex (contact point repeatable to +/-2 ticks across 16 probes). Speed 150 is NOT the ceiling; no ceiling was found at all.", "scope": {"name": "arm101-cli", "visibility": "public"}, "metadata": {"type": "note", "record_metadata": {"source": "hardware-run", "area": "arm101/hardware/arm_spec.py", "issue": "35,34", "port": "/dev/ttyACM1", "version": "0.22.0", "severity": "major"}, "created": "2026-07-12T14:08:50.006462+00:00", "last_recall": null, "recall_count": 0, "links": ["rezero-proven-on-hardware-2026-07-12", "arm-measures-its-own-walls", "sts3215-offset-register-facts", "arm-explore-grid-is-structurally-wrong"], "supersedes": null, "lifecycle": "active", "added_by": null}}
{"id": "arm-limits-rolling-frame-spec-2026-07-12", "hash": "ad3959942e012ce0398e9240937006cd7c947086e90e33a98196204b6cd3bde0", "content": "SPEC CONVERGED + PR #44 (2026-07-12, v0.22.2, /think -> /spec-to-plan): `arm limits` \u2014 the arm measures its own joint travel. Handles issue #43 (the software bound lands EXACTLY on the encoder seam for every factory-offset joint: reported 4095 under Ofs=85 is raw 84, one tick below the seam at raw 85).\n\nTHE MECHANISM \u2014 THE ROLLING FRAME. Breaks the chicken-and-egg (you cannot measure a joint's unreachable arc until you can see past the seam; you cannot evict the seam until you know the arc). Escape: write a TEMPORARY offset mapping the joint's CURRENT raw position to reported 2048 \u2014 the seam is then half a turn away, the farthest it can possibly be, with ~2048 clear ticks each side. Creep outward, and RE-CENTRE whenever the creep nears the reported bound. The seam is rolled ahead of the joint and never obstructs it, so travel is unbounded by the frame. Displacement accumulates in RAW ticks (frame-independent), so the measurement survives the frame moving underneath it. Termination by accumulated raw displacement: a full 4096 ticks with no wall => the joint is CONTINUOUS and no offset can ever help it \u2014 which is how the verb DERIVES wrist_roll's known verdict instead of being told it.\n\nUSER RULING 1 \u2014 NAME THE FAKE WALL HONESTLY. gentle_move calls contact when load is high AND the joint has stopped advancing. A gravity-loaded joint (shoulder_lift carries the whole arm) lifting against the 500 Torque_Limit cap can stall at high load with NOTHING in front of it. FOUR verdicts, carried PER END not per joint: WALL / TORQUE-LIMITED / EDGE / TIMEOUT. A TORQUE-LIMITED end is a LOWER BOUND, never a wall. KEY INSIGHT that makes naming it sufficient: this failure is SAFE \u2014 a torque-limited stall makes a range too NARROW, never too wide, so the arm UNDER-claims its reach. arm_spec therefore stores 'the widest envelope OBSERVED', not 'the mechanical limit'.\n\nCOROLLARY \u2014 THE MAX-OVER-POSES RULE IS ONLY HALF TRUE. 'Mechanical = the widest envelope across poses, because an obstacle can only ever make a range smaller' is SOUND for a WALL and FALSE for a TORQUE-LIMITED end: the arm's own weakness is not an obstacle you can pose your way out of \u2014 it shrinks the range in EVERY pose, so no number of poses ever escapes it. A torque-limited end stays a lower bound FOREVER and must never be promoted on pose evidence alone.\n\nUSER RULING 2 \u2014 PERSIST RAW TICKS EVERYWHERE. A re-zero invalidates every recorded tick ONLY BECAUSE we persist REPORTED ticks, which are a view through the current offset. RAW ticks are INVARIANT under a re-zero (the offset changes, the encoder does not). So arm_spec + the map store RAW; reported is a display view converted at the bus edge. This ELIMINATES the failure mode rather than detecting it \u2014 the calibration-identity machinery proposed in the #36 spec is NOT needed to protect data that cannot go stale. REZERO_ARCS already made this move in PR #41.\n\nSTOP-GATE (plan task t12): if the verb cannot re-derive the two answers we ALREADY know \u2014 elbow_flex => BOUNDED, wrist_roll => CONTINUOUS \u2014 from measurement alone, it is not measuring, it is guessing, and NO verdict it gives on the other four joints may be believed. Everything downstream halts and returns to the user.\n\nFALSIFIABLE ACCUSATIONS the frame forced (each can prove this work unnecessary): (a) arm_spec._REZERO_UNNECESSARY asserts 'four of the six joints do not wrap' \u2014 if all four come back with the seam OUTSIDE their travel, the message was RIGHT and the claim is retracted; (b) >=1 joint's measured span must differ from its EEPROM span by >100 ticks \u2014 if none does, the rationale for blocking #34 on this work is FALSE and must be withdrawn.\n\nPLAN: 15 tasks / 10 waves. Wave 0 = 4 file-disjoint pure tasks (raw-tick boundary, four-verdict record, crash-safe calibration journal, guard tests incl. 'nothing writes servo addr 9/11' \u2014 LeRobot's write_calibration DOES write them and must NOT be ported: addrs 9/11 are Min/Max_Position_Limit and CLAMP goals in servo mode, which would narrow the very reachable set this work recovers). Then the probe engine (serial: rolling frame -> creep -> classifier -> measured arcs), the verb, then 4 human-gated hardware waves.", "scope": {"name": "arm101-cli", "visibility": "public"}, "metadata": {"type": "decision", "record_metadata": {"source": "think-spec", "area": "arm101/hardware/", "issue": "43,35,34", "pr": "44", "version": "0.22.2", "spec": "docs/specs/2026-07-12-arm101-finds-its-own-joint-limits-a-new-gated-verb.md", "plan": "docs/plans/2026-07-12-arm101-finds-its-own-joint-limits-a-new-gated-verb.md", "status": "spec-converged, build in progress"}, "created": "2026-07-12T17:39:13.978719+00:00", "last_recall": null, "recall_count": 0, "links": [], "supersedes": null, "lifecycle": "active", "added_by": null}}
Loading
Loading