Skip to content

fix(fold): require a settled hinge before reporting half-open - #2734

Merged
thymikee merged 1 commit into
mainfrom
fix/fold-half-open-settled-pose
Sep 21, 2026
Merged

thymikee merged 1 commit into
mainfrom
fix/fold-half-open-settled-pose

Conversation

@thymikee

Copy link
Copy Markdown
Member

Summary

fold half-open reported the pose whenever its four-read budget happened to end on a half-open angle. The interval is open, so a hinge travelling between the end stops passes through it on every fold: an observed category was being returned as a settled pose.

half-open now succeeds only on two consecutive readings that both classify as half-open and differ by at most the existing 0.5°. closed and open keep the end-stop rule; attempt count, per-read timeout, and cancellation are unchanged. A budget ending on an unsettled half-open angle fails with details.reason: "fold-pose-unsettled" plus requestedPose, observedPose, hingeAngleDegrees, previousHingeAngleDegrees, saying the hinge was observed half-open and did not settle; a budget ending on another pose keeps fold-pose-unverified. Supersedes ADR 0025's budget-end exception, recorded there as an amendment.

Six files: implementation, colocated tests, ADR 0025, versioned help, user docs, CHANGELOG. Closes #2730.

Validation

pnpm check:affected --run passed on 4d3654a8b; pose.test.ts 11/11.

Regression evidence: against the pre-fix pose.ts, the two new cases fail (promise resolved "{ pose: 'half-open', ... }" instead of rejecting) and pass after the fix. Mutations killed: dropping the previous-reading category check, restoring the budget-end success, routing closed through the settle rule, removing the loop's abort guard.

Live on an iPhone Duo (iOS 27.1) at this head: fold open reported 180°; fold half-open from that open pose reported 130° naming LCD-1 at 669x951pt; a manual devicectl device motion hinge-angle stream then read Angle:130,0° Velocity:+0,0°/s, so the reported angle is where the hinge rests. The fold-pose-unsettled path itself is proven only by the mocked counterexample (170 → 150 → 120 → 90) — it cannot be forced on hardware on demand. Manual session closed, daemon stopped, no stale device claims. Provider integration and coverage run on CI.

`fold half-open` accepted the requested category as the answer whenever its four-read budget
happened to end on a half-open angle. A hinge travelling between the end stops passes through that
interval on every fold, so the command could report a Book pose for a hinge that was still moving
and had no intention of resting at 130 degrees.

An angle inside the open interval proves the category and nothing else, so the pose is now the hinge
*resting* there: two consecutive readings that both classify as half-open and differ by at most the
existing 0.5-degree tolerance. Numerical proximity is not agreement when a pair straddles a category
boundary, so `isSettledHalfOpenPair` tests both readings, not just their distance. `closed` and
`open` keep the end-stop rule, and the attempt count, per-read timeout, and cancellation behaviour
are untouched.

A budget that ends on an unsettled half-open angle fails with COMMAND_FAILED and
`details.reason: "fold-pose-unsettled"`, carrying requestedPose, observedPose, hingeAngleDegrees and
the previous reading, and says the hinge was observed half-open and did not settle — its own observed
pose refutes "did not reach". A budget ending on another pose keeps `fold-pose-unverified`. This
supersedes ADR 0025's budget-end exception, recorded there as an amendment with the Duo run taken
under the new rule.

Closes #2730
@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-21 16:47 UTC

@github-actions

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.73 MB 4.73 MB +918 B
Package (unpacked) 4.73 MB 4.73 MB +918 B
Package (download) 1.41 MB 1.41 MB +263 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 29.5 ms 29.5 ms +0.0 ms
CLI --help 83.3 ms 84.0 ms +0.7 ms

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed 4d3654a. half-open now needs two consecutive half-open readings within 0.5°, closed and open keep the end-stop rule, and the attempt count, per-read timeout and cancellation are unchanged. I found no issue, all checks pass, and there are no conflicts. The fold-pose-unsettled failure is covered by the mocked test only; the live Duo run covers the settled path.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 21, 2026
@thymikee
thymikee merged commit 6cb160a into main Sep 21, 2026
20 checks passed
@thymikee
thymikee deleted the fix/fold-half-open-settled-pose branch September 21, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(fold): distinguish an observed half-open angle from a settled pose

1 participant