# FOCAS Docker simulator — focas-mock + shim DLL

Hardware-free FOCAS fixture for OtOpcUa's integration test matrix. Runs
the vendored [`focas-mock`](focas-mock/VENDORED.md) Python server under
Docker and pairs it with the [shim DLL](../Shim/VENDORED.md) that
masquerades as `Fwlib64.dll` inside the .NET test process.

## Architecture

```
┌────────────────────────────┐   cnc_allclibhndl3 / cnc_rdparam / ...
│  xunit test process        │        (P/Invoke, __stdcall)
│   ├── Driver.FOCAS         │
│   │    └── FwlibNative.cs ─┼─┐
│   └── FocasSimFixture      │ │ resolves to...
└────────────────────────────┘ │
                               ▼
                ┌────────────────────────────┐
                │ Fwlib64.dll (shim)         │  JSON over TCP
                │  tests/.../Shim/focas_     │──────────────────────┐
                │  shim.c compiled here      │                      │
                └────────────────────────────┘                      │
                                                                    ▼
                                                ┌─────────────────────────────┐
                                                │ focas-mock (Docker)         │
                                                │   python:3.11-slim          │
                                                │   profile-aware responses   │
                                                │   mock_load_profile /       │
                                                │   mock_patch admin methods  │
                                                └─────────────────────────────┘
```

The shim bridges the binary ABI (C `__stdcall` exports with FOCAS struct
shapes) to the mock's newline-delimited JSON protocol. OtOpcUa's
`FocasSimFixture` seeds per-test state by sending `mock_load_profile` +
`mock_patch` admin calls on the same socket. Tests assert the managed
driver sees the seeded values through its normal P/Invoke path.

## Running

Pick one compose profile (they all publish 8193 — only one at a time):

```powershell
docker compose -f Docker/docker-compose.yml --profile thirtyone up -d
dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests
docker compose -f Docker/docker-compose.yml --profile thirtyone down
```

Available profiles + their focas-mock target:

| compose --profile | focas-mock profile | Covers |
|---|---|---|
| `thirtyone` / `thirty` / `thirtytwo` | `fwlib30i64` | 30i / 31i / 32i series |
| `sixteen` | `FWLIB64` | 16i / 18i / 21i legacy family |
| `zerod` / `zerof` / `zeromf` / `zerotf` | `fwlib0iD64` | 0i-D / 0i-F / 0i-MF / 0i-TF |
| `powermotion` | `fwlib0DN64` | Power Motion i |
| `ethernet` | `fwlibe64` | Ethernet-variant DLL |
| `ncguide` | `fwlibNCG64` | NC Guide PC simulator |

## What this covers — and what it doesn't

**Covered:**

- All 10 FOCAS functions `FwlibNative.cs` P/Invokes
- Read-after-write round-trip for parameters, macros, PMC ranges
- PMC bit read-modify-write path (via the `pmc_wrpmcrng` seam)
- `IAlarmSource` raise + clear transitions (via `mock_schedule_alarms`)
- Per-series profile selection — tests can pin one and assert series-gated
  behaviour

**Not covered** (still hardware-gated):

- Real FOCAS2 TCP wire protocol (this is a JSON mock; the shim hides
  the real protocol entirely)
- CNC-specific firmware quirks (position scaling across power cycles,
  edit-mode session locks, MTB custom screens)
- Concurrent-read behaviour on the real `Fwlib64.dll` — the shim is
  single-threaded per connection

See [`docs/drivers/FOCAS-Test-Fixture.md`](../../../docs/drivers/FOCAS-Test-Fixture.md)
for the full coverage map.

## Skip behaviour

`FocasSimFixture` probes the mock at collection init time:

- Mock unreachable → tests skip with the compose-up command to run
- Mock reachable but shim DLL not loaded → tests skip with a pointer
  at `Shim/build.ps1`
- Both available → tests run

This lets the same test assembly be green on a fresh CI box without
docker, green on a dev box with just the docker compose up, and
exercise the full wire path when the shim is built.
