Skip to content

spec+plan: the arm measures its own joint travel — rolling the encoder seam out of the way (#43)#44

Open
OriNachum wants to merge 2 commits into
mainfrom
spec/arm-finds-its-own-joint-limits
Open

spec+plan: the arm measures its own joint travel — rolling the encoder seam out of the way (#43)#44
OriNachum wants to merge 2 commits into
mainfrom
spec/arm-finds-its-own-joint-limits

Conversation

@OriNachum

Copy link
Copy Markdown
Contributor

Carries the converged devague spec and its buildable plan for issue #43. No runtime code yet — the build follows via /assign-to-workforce.

The finding this answers

arm explore's bounds have been artifacts of our own clamp. The software bound lands exactly ON the encoder seam for every factory-offset joint: five of six still carry Ofs = 85, the commandable bound is reported tick 4095, and 4095 under Ofs=85 is raw 84 — one tick below the seam at raw 85.

Three joints reached that bound with no contact — still free, 2 / 3 / 11 raw ticks short. shoulder_lift then sagged through the seam under gravity with torque off, and now reads 2. Nothing commanded it there. So #35 is potentially a six-joint problem, not two, and every per-joint range recorded before now is suspect at its high end.

The mechanism: a rolling frame

The chicken-and-egg is that you cannot measure a joint's unreachable arc until you can see past the seam, and you cannot evict the seam until you know the arc.

Write a temporary offset mapping the joint's current raw position to reported 2048 — 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 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, which are frame-independent — so the measurement survives the frame moving underneath it. Termination is by accumulated raw displacement: a full 4096 ticks with no wall means the joint is continuous and no offset can ever help it. That is how the verb derives wrist_roll's known verdict rather than being told it.

Two rulings that shape the build

Name the fake wall honestly. gentle_move calls contact when load is high and the joint stops advancing — but a gravity-loaded joint lifting the arm's own weight against the 500 torque cap can stall with nothing in front of it. Four verdicts (WALL / TORQUE-LIMITED / EDGE / TIMEOUT), carried per end. A torque-limited end is a lower bound, never a wall.

This failure is safe, which is why naming it is enough: a torque-limited stall makes a range too narrow, never too wide — the arm under-claims its reach. So arm_spec stores "the widest envelope observed", not "the mechanical limit", and says which ends it can vouch for.

It also scopes the max-over-poses rule honestly. That rule is sound for an obstacle — obstacles shrink ranges, and taking the max over poses undoes that. It is false for the arm's own weakness, which shrinks the range in every pose, so no number of poses ever escapes it.

Persist raw ticks everywhere. A re-zero invalidates history 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. REZERO_ARCS already made this move in PR #41; this finishes it. It eliminates the failure mode rather than detecting it, so the calibration-identity machinery the #36 spec proposed is not needed to protect data that cannot go stale.

The plan — 15 tasks, 10 waves

Wave 0 is four file-disjoint pure tasks (raw-tick boundary, the four-verdict record, the crash-safe calibration journal, guard tests). Then the probe engine strictly serial (rolling frame → creep → classifier → measured arcs), then the verb, then four human-gated hardware waves.

t12 is a stop-gate. If the verb cannot re-derive the two answers we already know — elbow_flex BOUNDED, wrist_roll CONTINUOUS — 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.

Claims that can prove themselves wrong

The frame forced every accusation to carry a falsification condition:

  • "our own code lies"_REZERO_UNNECESSARY currently asserts four joints don't wrap. If all four come back with the seam outside their travel, the message was right, the claim is retracted, and no code changes.
  • "this is why the grid was garbage" — at least one joint's measured span must differ from its EEPROM span by >100 ticks. If none does, the rationale for blocking arm explore's grid is derived from the wrong things — EEPROM bounds and a global bucket size (r2) #34 on this work is false and the report must say so.
  • four verdicts — each must be producible by a test. A verdict no test can generate is one the code cannot tell from its neighbours: not four verdicts, one lie with four names.

Artifacts

  • docs/specs/2026-07-12-arm101-finds-its-own-joint-limits-a-new-gated-verb.md
  • docs/plans/2026-07-12-arm101-finds-its-own-joint-limits-a-new-gated-verb.md
  • .devague/ frame + plan state

Refs #43 #35 #34

Issue #43 found that 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. Three joints reached that bound with no contact — still free.
The arm has been reporting the seam as its boundary, so no joint's real travel
could ever be seen past it.

The spec's mechanism is a ROLLING FRAME: write a temporary offset that maps the
joint's current raw position to reported 2048, putting the seam half a turn away,
then creep outward and RE-CENTRE whenever the creep nears the bound. The seam is
rolled ahead of the joint and never obstructs it. Displacement accumulates in RAW
ticks, which are frame-independent, so the measurement survives the frame moving
underneath it.

Two user rulings shape the build:

- NAME THE FAKE WALL HONESTLY. A gravity-loaded joint can stall against the torque
  cap with nothing in front of it. Four verdicts (WALL / TORQUE-LIMITED / EDGE /
  TIMEOUT), carried per END. A torque-limited end is a lower bound, never a wall.
  This failure is SAFE — it makes a range too narrow, never too wide.
- PERSIST RAW TICKS EVERYWHERE. A re-zero invalidates history only because we
  persist REPORTED ticks, which are a view through the current offset. Raw ticks
  are invariant under a re-zero. This eliminates the failure mode rather than
  detecting it, so no calibration-identity machinery is needed.

15 tasks, 10 waves. t12 is a STOP-GATE: if the verb cannot re-derive the two
answers we already know (elbow_flex BOUNDED, wrist_roll CONTINUOUS) from
measurement alone, it is guessing, and everything downstream halts.

Refs #43 #35 #34

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vrVNSJ9kzhCQqwpA2e8u6
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add spec+plan for "arm limits" rolling encoder seam out of the way (issue #43)

📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Add a converged spec for measuring true joint travel past the encoder seam.
• Add a build plan (15 tasks, 10 waves) with falsifiable acceptance gates.
• Bump version/changelog to publish the spec+plan artifacts.
Diagram

graph TD
  A["Spec: arm limits"] --> B["Plan: 15 tasks"] --> C[(".devague frame/plan")
  D["pyproject.toml"] --> E["CHANGELOG.md"]
  F[".eidetic memory"] --> C
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep only Markdown (no .devague JSON exports)
  • ➕ Less repo noise/churn from generated JSON
  • ➕ Easier to review diffs; fewer large machine-formatted files
  • ➖ Loses structured task dependency/acceptance metadata used by the workflow
  • ➖ Harder to mechanically validate plan consistency across tools
2. Use ADR-style decision records + smaller plan checklist
  • ➕ Makes key decisions easier to skim/review
  • ➕ Reduces single-document size and cognitive load
  • ➖ Splits the narrative across multiple files
  • ➖ May weaken the explicit, testable gate/criteria structure captured in the current plan

Recommendation: Current approach is appropriate if the repo already treats devague exports as first-class artifacts: the spec is explicit, falsifiable, and the plan encodes hard safety/acceptance gates (notably the elbow_flex/wrist_roll stop-gate). If review burden becomes an issue, consider trimming or regenerating-only .devague JSON in CI while keeping Markdown as the canonical human-reviewed source.

Files changed (9) +1303 / -9

Documentation (6) +1300 / -6
arm101-finds-its-own-joint-limits-a-new-gated-verb.jsonAdd exported devague frame for rolling-frame joint limit measurement +487/-0

Add exported devague frame for rolling-frame joint limit measurement

• Introduces the structured frame capturing claims, decisions, requirements, and falsification conditions for the new gated 'arm limits' verb.

.devague/frames/arm101-finds-its-own-joint-limits-a-new-gated-verb.json

arm101-finds-its-own-joint-limits-a-new-gated-verb.jsonAdd exported devague plan with 15 tasks and hardware gates +547/-0

Add exported devague plan with 15 tasks and hardware gates

• Adds the machine-readable build plan (tasks, deps, acceptance criteria, risks) for implementing the rolling-frame joint travel measurement and commit flow.

.devague/plans/arm101-finds-its-own-joint-limits-a-new-gated-verb.json

arm101-cli__public.jsonlRefresh public memory log entries referenced by the new spec/plan +6/-6

Refresh public memory log entries referenced by the new spec/plan

• Updates/extends persisted notes (e.g., wrist_roll re-zero constraints, offset register facts, seam verification traps, and related session state) to align with the new spec/plan context.

.eidetic/memory/arm101-cli__public.jsonl

CHANGELOG.mdAdd 0.22.2 entry for the new spec+plan artifacts +6/-0

Add 0.22.2 entry for the new spec+plan artifacts

• Documents the addition of the 'arm limits' spec+plan and the rolling encoder-seam approach under version 0.22.2.

CHANGELOG.md

2026-07-12-arm101-finds-its-own-joint-limits-a-new-gated-verb.mdAdd human-readable build plan for 'arm limits' +156/-0

Add human-readable build plan for 'arm limits'

• Adds the Markdown plan mirroring the exported JSON: 15 tasks, dependencies, acceptance criteria, and hardware stop-gates.

docs/plans/2026-07-12-arm101-finds-its-own-joint-limits-a-new-gated-verb.md

2026-07-12-arm101-finds-its-own-joint-limits-a-new-gated-verb.mdAdd spec for rolling-frame joint-limit measurement and classification +98/-0

Add spec for rolling-frame joint-limit measurement and classification

• Defines the rolling-frame mechanism, raw-tick persistence requirement, four end-verdicts, bounded-vs-continuous classifier, and gated measure vs commit separation.

docs/specs/2026-07-12-arm101-finds-its-own-joint-limits-a-new-gated-verb.md

Other (3) +3 / -3
currentPoint devague 'current' to the new arm limits spec +1/-1

Point devague 'current' to the new arm limits spec

• Updates the devague pointer to the new spec/plan slug for issue #43 work.

.devague/current

current_planPoint devague 'current_plan' to the new arm limits plan +1/-1

Point devague 'current_plan' to the new arm limits plan

• Updates the devague plan pointer to the new arm101 joint-limits plan slug.

.devague/current_plan

pyproject.tomlBump package version to 0.22.2 +1/-1

Bump package version to 0.22.2

• Updates the project version to match the changelog entry for publishing the spec+plan artifacts.

pyproject.toml

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 8 rules
✅ Skills: cicd

Grey Divider


Remediation recommended

1. Stale uv.lock version 🐞 Bug ☼ Reliability
Description
pyproject.toml bumps the project version to 0.22.2 but uv.lock still lists the editable root
package as 0.22.1, leaving the committed lockfile inconsistent with the project metadata. This can
force CI/dev installs to modify the working tree during uv sync (or fail in locked/frozen modes),
reducing reproducibility.
Code

pyproject.toml[R1-3]

[project]
name = "arm101-cli"
-version = "0.22.1"
+version = "0.22.2"
Relevance

⭐⭐⭐ High

Repo typically updates uv.lock alongside version bumps (e.g., pyproject+uv.lock changed together in
PR #42).

PR-#42

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The repo’s declared version is now 0.22.2, but the lockfile still records the root editable package
as 0.22.1; CI uses uv sync, so a stale committed lock can lead to non-reproducible installs or
lockfile rewrites/failures depending on mode.

pyproject.toml[1-4]
uv.lock[5-8]
.github/workflows/tests.yml[26-32]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The PR bumps `pyproject.toml` to version `0.22.2`, but the committed `uv.lock` still records the root editable package (`arm101-cli`) as `0.22.1`. This makes the lockfile stale relative to the project metadata.

### Issue Context
CI runs `uv sync`, which relies on `uv.lock` for reproducible environments. When `uv.lock` is stale, `uv sync` may rewrite the lockfile (dirty working tree) or fail in workflows that use locked/frozen modes.

### Fix Focus Areas
- pyproject.toml[1-4]
- uv.lock[5-8]

### Expected fix
1. Regenerate the lockfile (e.g. `uv lock`, or the repo’s standard lock update command).
2. Verify `uv.lock` now records `arm101-cli` as `0.22.2`.
3. Commit the updated `uv.lock`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Eidetic entry duplicates changelog 📘 Rule violation ⚙ Maintainability
Description
The updated eidetic memory entry arm-explore-rebuild-state-2026-07-12 mostly restates
release/status information that is already recorded in repository files (e.g., CHANGELOG.md). This
makes the eidetic store redundant and prone to staleness, violating the rule against storing
repository-derivable information there.
Code

.eidetic/memory/arm101-cli__public.jsonl[16]

+{"id": "arm-explore-rebuild-state-2026-07-12", "hash": "fd81c2c306a1f6a022a691980850c13e8f68535bcafcbaf93c7db3f0c6c2b7b2", "content": "STATE OF THE arm explore REBUILD (2026-07-12, main @ v0.21.0, 1202 tests). Three PRs merged today off the converged spec docs/specs/2026-07-12-arm-explore-now-maps-the-arm-s-real-joint-space-it.md and its 21-task plan:\n\n- PR #38 (v0.19.x) \u2014 CLOSES #33. torque_guard: motion verbs release the arm on any ABNORMAL exit (exception/bus fault/SIGINT). HOLD ON SUCCESS, RELEASE ON ABNORMAL (clean exit = ZERO release writes, so gentle_move stop-and-hold survives).\n- PR #39 (v0.20.x) \u2014 `arm profile <joint>`: ramps Goal_Speed and finds the highest speed at which CONTACT DETECTION STILL WORKS (not merely the highest the servo survives). Structurally refuses to certify a speed on free motion alone.\n- PR #40 (v0.21.0) \u2014 encoder linearity (#35 code, NOT yet hardware-verified): `arm rezero` + --verify, offset primitives (addr 31), wrist_roll soft limit + enforcement, and the t5 spike doc.\n\nSTILL OPEN AND WHY:\n- #35 stays OPEN deliberately \u2014 the code shipped but the SEAM-EVICTION PROOF has not run on hardware. docs/hardware-rezero-procedure.md is the procedure; step 6 (torque-off hand-sweep of elbow_flex through its full travel, asserting monotonic position) is what settles whether the servo actually MOVES the seam. If it reports `seam-not-evicted`, the re-zero achieves nothing and wave 2a returns to the user for a re-decision (the ARCS fallback was explicitly NOT chosen).\n- #34 (grid) \u2014 t16 (per-joint bucket sizes from span + target bucket COUNT) is BUILT on branch feat/explore-grid, NOT yet PR`d. t17 (bounds from a supplied map, EEPROM only as fallback) and t18 (honest probe-cost budget + pre-flight wall-clock estimate) are NOT built and are BLOCKED on t15 \u2014 the hardware run of `arm profile` that yields real per-joint speeds.\n- #37 \u2014 does the STS3215 have a comms-loss torque watchdog? Filed because a physical USB yank CANNOT be recovered in software (12V powers the servos; USB is only data).\n\nHUMAN-GATED HARDWARE STILL OWED: t4 (SIGINT + port-contention safety acceptance), t6 (re-confirm elbow_flex wrap BEFORE re-zeroing destroys the evidence), t5-hardware/t11 (the rezero power-cycle + seam sweep), t12/t15 (baseline timing + the arm profile run).", "scope": {"name": "arm101-cli", "visibility": "public"}, "metadata": {"type": "note", "record_metadata": {"source": "session", "area": "arm101", "version": "0.21.0", "issues": "33,34,35,37"}, "created": "2026-07-12T10:51:26.916283+00:00", "last_recall": "2026-07-12T15:34:29.039636+00:00", "recall_count": 1, "links": ["wrist-roll-cannot-be-re-zeroed", "sts3215-offset-register-facts", "seam-verification-false-pass-traps", "torque-release-must-use-clear-overload", "usb-yank-cannot-be-fixed-in-software"], "supersedes": null, "lifecycle": "active", "added_by": null}}
Relevance

⭐ Low

Same suggestion to remove/trim this exact eidetic state entry was rejected in PR #41.

PR-#41

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The compliance rule forbids eidetic entries that simply restate information available in the repo.
The new/updated eidetic entry at .eidetic/memory/arm101-cli__public.jsonl:16 is a release/status
summary, while CHANGELOG.md already records the same kinds of release details (features
added/changed/fixed by version), making the eidetic content repository-derivable.

Rule 1497307: Avoid storing repository-derivable information in the eidetic memory store
.eidetic/memory/arm101-cli__public.jsonl[16-16]
CHANGELOG.md[30-57]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The eidetic memory store contains an entry whose content is largely derivable from the repository (notably the changelog/release notes), which violates the guideline to avoid storing repo-derivable information in `.eidetic/memory`.

## Issue Context
The entry `arm-explore-rebuild-state-2026-07-12` summarizes merged PRs/versions and project status that can be reconstructed from `CHANGELOG.md` and git history, making it likely to drift.

## Fix Focus Areas
- .eidetic/memory/arm101-cli__public.jsonl[16-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread pyproject.toml
Comment on lines 1 to +3
[project]
name = "arm101-cli"
version = "0.22.1"
version = "0.22.2"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Stale uv.lock version 🐞 Bug ☼ Reliability

pyproject.toml bumps the project version to 0.22.2 but uv.lock still lists the editable root
package as 0.22.1, leaving the committed lockfile inconsistent with the project metadata. This can
force CI/dev installs to modify the working tree during uv sync (or fail in locked/frozen modes),
reducing reproducibility.
Agent Prompt
### Issue description
The PR bumps `pyproject.toml` to version `0.22.2`, but the committed `uv.lock` still records the root editable package (`arm101-cli`) as `0.22.1`. This makes the lockfile stale relative to the project metadata.

### Issue Context
CI runs `uv sync`, which relies on `uv.lock` for reproducible environments. When `uv.lock` is stale, `uv sync` may rewrite the lockfile (dirty working tree) or fail in workflows that use locked/frozen modes.

### Fix Focus Areas
- pyproject.toml[1-4]
- uv.lock[5-8]

### Expected fix
1. Regenerate the lockfile (e.g. `uv lock`, or the repo’s standard lock update command).
2. Verify `uv.lock` now records `arm101-cli` as `0.22.2`.
3. Commit the updated `uv.lock`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

…by Ofs=85

The claim as written said the software bound lands on the seam "for every
factory-offset joint", implying it is an artifact of the factory Ofs=85. It is
not. reported = (raw - Ofs) mod 4096, so reported 0 IS the seam and reported 4095
is the tick immediately below it — for EVERY offset, including 0. A re-zero can
never move the bound off the seam; it can only move the seam to a raw angle the
joint cannot physically reach.

So the actual bug is not that the bound coincides with the seam (it always does),
but WHERE the seam sits: at Ofs=85 it lands at raw 85, INSIDE these joints'
travel, so they hit the bound while still physically free.

Both ends of the commandable range are therefore pinned to the cut, and they are
physically adjacent on the shaft — worse than one end resting on it.

Found by task t4 of the build while pinning the seam arithmetic. Left uncorrected,
a later task would have reasoned from "re-zero fixes the bound", which is false.

Refs #43

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vrVNSJ9kzhCQqwpA2e8u6
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant