Auto: focas-f5a — cycle time per part / last cycle delta

Closes #272
This commit is contained in:
Joseph Doherty
2026-04-26 09:11:21 -04:00
parent 45770e8d90
commit e3d7c65f61
7 changed files with 711 additions and 9 deletions

View File

@@ -304,6 +304,82 @@ Bit-level writes never appear here as a separate kind — they reach the
simulator as 1-byte writes after the driver's RMW wrapper, so the audit
shape is identical to a byte write at the same address.
## Cycle-time per part / last cycle delta — F5-a (issue #272)
Plan PR F5-a derives `Production/LastCycleSeconds` +
`Production/LastCycleStartUtc` from the existing `cnc_rdparam(6711)` +
`cnc_rdtimer` snapshot stream — **pure derivation, no new wire calls**.
The simulator does NOT need new wire commands; the existing
`cnc_rdparam` + `cnc_rdtimer` handlers already cover the read surface.
What focas-mock DOES need is an admin endpoint + test-fixture helper
that lets integration tests atomically increment the parts-count
counter alongside the cycle-time timer so the driver sees a clean
"cycle completed" transition on the next probe tick.
### Per-profile state
Already covered by the existing F1-b state map:
- `parameters: Dict[int, int]` (entry `6711` is the parts-count counter).
- `timers: Dict[int, int]` (entry `0` is the live cycle-time counter,
in seconds).
### Admin endpoint — `POST /admin/mock_simulate_cycle_completion`
Atomically advances both values to model "the CNC just finished a
cycle". Atomicity matters: the F5-a derivation samples both fields on
every probe tick, so if the simulator updated parts-count and the
timer in two separate writes the test could observe an intermediate
state where parts-count incremented but the timer hasn't updated yet
(producing a misleading `LastCycleSeconds`).
```
POST /admin/mock_simulate_cycle_completion
{
"profile": "Series30i",
"partsDelta": 1, // default 1; tests asserting backfill use 3+
"newCycleTimerSeconds": 18 // absolute value, NOT a delta
}
```
Handler steps:
1. `parameters[6711] += partsDelta` (under the per-profile lock).
2. `timers[0] = newCycleTimerSeconds`.
3. Return `200 OK` with the new values for verification.
The endpoint MUST hold the profile's update lock for the full
read-modify-write so a concurrent `cnc_rdparam` + `cnc_rdtimer` poll
sees both fields in their pre-update OR post-update state — never
half-applied.
### `FocasSimFixture.SimulateCycleCompletionAsync`
The future test-support helper wraps the admin endpoint:
```csharp
await fixture.SimulateCycleCompletionAsync(
profile: "Series30i",
partsDelta: 1,
newCycleTimerSeconds: 18);
```
Integration test `Series/CycleDeltaTests.cs` will assert:
- After a 5 -> 6 transition with `newCycleTimerSeconds=18`, the
driver's `Production/LastCycleSeconds` settles to `currentTimer -
prevTimer`.
- `Production/LastCycleStartUtc` is within driver-tolerance of
`nowUtc - LastCycleSeconds` (allow a small window for probe-tick
jitter).
- Counter reset (parts -> 0) preserves the last published values.
- Cycle-timer rollover does not publish a negative delta.
These tests are blocked on the focas-mock + integration-test project
landing; the unit-test coverage in `FocasCycleDeltaTests` already
exercises every same-process invariant of the derivation.
### Status
focas-mock simulator has not landed yet (tracked separately from F4-b /