# FOCAS simulator (focas-mock) plan

Notes on the focas-mock simulator that the FOCAS driver's integration
tests will eventually talk to. Today there is no FOCAS integration-test
project; this doc is the contract the future fixture will be built
against. Keeping the contract tracked in repo means the wire-protocol
command ids (and their request/response payloads) don't drift between the
.NET wire client and a future Python implementation.

## Ground rules

- Append-only command ids. Mirror
  [`focas-wire-protocol.md`](./focas-wire-protocol.md) verbatim.
- Per-profile state. The simulator hosts N CNC profiles concurrently
  (`Series0i`, `Series30i`, `PowerMotion`, ...). Each profile has its own
  alarm-history ring buffer + its own override map.
- Admin endpoints under `POST /admin/...` mutate state without going
  through the wire protocol; integration tests use these to seed canned
  inputs.

## Protocol surface (current scope)

| Cmd | API | State impact |
| --- | --- | --- |
| `0x0001` | `cnc_rdcncstat` | reads cached ODBST per profile |
| `0x0002` | `cnc_rdparam` | reads parameter map per profile |
| `0x0003` | `cnc_rdmacro` | reads macro variables per profile |
| `0x0004` | `cnc_rddiag` | reads diagnostic map per profile |
| `0x0010` | `pmc_rdpmcrng` | reads PMC byte ranges |
| `0x0020` | `cnc_modal` | reads cached modal MSTB per profile |
| ... | ... | ... |
| **`0x0F1A`** | **`cnc_rdalmhistry`** | **dumps the per-profile alarm-history ring buffer (issue #267, plan PR F3-a)** |

## `cnc_rdalmhistry` mock behaviour

The simulator keeps a per-profile ring buffer of alarm-history entries.
Default fixture seeds 5 profiles with 10 canned entries each (per the F3-a
plan).

### Request decode

```
[int16 LE depth]
```

### Response encode

Use `FocasAlarmHistoryDecoder.Encode` semantics in reverse: emit the
count followed by `ALMHIS_data` blocks padded to 4-byte boundaries. The
.NET-side decoder consumes the same format verbatim, so a Python encoder
written against the table in
[`focas-wire-protocol.md`](./focas-wire-protocol.md) interoperates without
extra glue.

### Admin endpoint — `POST /admin/mock_patch_alarmhistory`

Replaces the alarm-history ring buffer for a profile.

```
POST /admin/mock_patch_alarmhistory
{
  "profile": "Series30i",
  "entries": [
    {
      "occurrenceTime": "2025-04-01T09:30:00Z",
      "axisNo": 1,
      "alarmType": 2,
      "alarmNumber": 100,
      "message": "Spindle overload"
    },
    ...
  ]
}
```

`entries` order is interpreted as ring-buffer order (most-recent first to
match FANUC's natural surface).

### `FocasSimFixture.SeedAlarmHistoryAsync`

The future test-support helper wraps the admin endpoint:

```csharp
await fixture.SeedAlarmHistoryAsync(
    profile: "Series30i",
    entries: new []
    {
        new FocasAlarmHistoryEntry(
            new DateTimeOffset(2025, 4, 1, 9, 30, 0, TimeSpan.Zero),
            AxisNo: 1, AlarmType: 2, AlarmNumber: 100, Message: "Spindle overload"),
    });
```

Integration test `Series/AlarmHistoryProjectionTests.cs` will assert:

- historic events fire once with the seeded timestamps
- second poll yields zero new events (dedup honoured end-to-end)
- active-alarm raise/clear still works alongside the history poll

These tests are blocked on the focas-mock + integration-test project
landing; the unit-test coverage in `FocasAlarmProjectionTests` already
exercises every same-process invariant.
