forgetest: the fixture spaces its presses; an unattended refusal is an error; the baseline resets out of a hold

The first campaign with the bench actuator wired failed
motion.button-hold-resume on the tool, not the machine: the second press
was asked while the first 200 ms pulse was still on, the fixture answered
409, the runner handed the step to an operator who was not in the room,
and the post pass could not jog a controller left in Hold.

- fixture.py: a press waits for the last pulse to end (the fixture's
  pulse_ms) plus a 300 ms release, so the controller sees the edge; a
  409 for a pulse in progress is waited out against button_pulsing and
  retried once.
- runner.py: in an unattended run a fixture refusal ends the test at
  once as ERROR naming the refusal; the operator fallback stays for
  attended runs.
- baseline.py: a controller in Hold or Door gets a soft reset before the
  return jog, position kept.
- tests: the fake fixture refuses a press while one is in progress and
  reports button_pulsing; FakeGrbl records ^X and can land a reset in a
  chosen state; five new tests.
- docs: ACCEPTANCE.md fixture rules, fixture/README.md tool's side.

No catalog consequence: tool-side change, no covers map moves.
Bench: campaign c-20260824174545-0bdc 25/25 with every action by the
fixture; the hold reset proven by a dry drill.
This commit is contained in:
ScottW514
2026-08-24 14:02:59 -04:00
parent e99888753f
commit 00ded74189
8 changed files with 147 additions and 5 deletions
+5 -1
View File
@@ -281,7 +281,11 @@ most every 30 s otherwise. What holds:
read the switches; `ctx.act` asks it and then waits for forgectrl's
reading exactly as it waits for an operator's hand. An action the box
fails to perform falls back to the operator's notice, and the record
says so (`evidence.actions[].by`, `fixture_error`).
says so (`evidence.actions[].by`, `fixture_error`); in an unattended
run there is nobody to fall back to, and the test ends ERROR naming
the refusal. Two button presses are spaced by the tool (the last
pulse's end plus 300 ms), so the controller sees the release between
them and not one long press.
- **An operator test the fixture can run alone runs unattended.** A test
declares its actions and, with `hands=(...)`, whatever else it asks of
a person ("app" for a job in the Glowforge app). An `operator` test