e84: SEMI §6 handshake timers TA1/TA2/TA3

E84StateMachine had the full signal-level handshake but no timer
enforcement.  In a real AMHS that's a deadlock: if equipment is slow to
assert L_REQ / U_REQ, or AMHS is slow to assert BUSY / COMPT, neither
side notices — the wires just sit stuck.  SEMI E84 §6 mandates three
timers that bound each leg of the dance.

TA1 — armed in ValidAsserted, cancelled in Load/UnloadReady.
      AMHS bounds how long equipment takes to acknowledge VALID.
TA2 — armed in Load/UnloadReady, cancelled in Transferring.
      Equipment bounds how long AMHS takes to start the transfer.
TA3 — armed in Transferring, cancelled on Complete.
      Equipment bounds the BUSY-phase duration.

The FSM stays I/O-free (it's the design invariant): arm/cancel are
delivered via callbacks, the application owns the asio::steady_timer,
and the application calls `fsm.on_timeout(id)` when its real clock
fires.  Stale on_timeout calls (post-cancel race) are no-ops.

On expiry, the FSM transitions to a new `HandoffFault` state, records
the `E84Fault` reason, fires the optional fault_handler, and latches
the fault until `reset()`.  Signal jitter on the wires cannot silently
clear a recorded handshake timeout — once you've crossed the timer,
you stop.

Defaults are all-zero, which disables arming.  This is what every
existing test relies on, and what back-to-back simulation (no
wall-clock) needs.  Production tools call `set_timeouts({2s, 2s, 60s})`
or whatever their port spec dictates.

12 new test cases / 59 assertions: arming per state, cancelling per
exit, expiry-to-fault for all three timers, ES cancels everything,
stale-expiry no-op, fault latching across signal jitter, and a
full-cycle arm/cancel trace.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 14:03:10 +02:00
parent a4419e15cd
commit 2ea3ab796a
5 changed files with 483 additions and 5 deletions
+1
View File
@@ -197,6 +197,7 @@ Legend:
| Capability | Status | Spec ref | Messages | Notes |
|---------------------------------------|--------|----------|----------|-------|
| Handoff state machine | ✅ | E84 | — | Full LOAD / UNLOAD signal vocabulary (CS_0/CS_1, VALID, TR_REQ, BUSY, COMPT, AM_AVBL, ES). Per-port via `E84PortStore` keyed by `port_id`; independent FSMs run in parallel per load port. |
| Handshake timers TA1 / TA2 / TA3 | ✅ | E84 §6 | — | `E84StateMachine::set_timeouts({ta1, ta2, ta3})` + `set_timer_handlers(arm, cancel)`. TA1 armed in ValidAsserted, TA2 in Load/UnloadReady, TA3 in Transferring; cancelled on the matching transition out. Expiry transitions to `HandoffFault` (latched until `reset()`). FSM stays I/O-free — application drives the real clock (asio::steady_timer in the reference server). |
## 4j. E5 §13 Wafer Maps (S12)